X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/73aca81b50073109b137bba659d38af11d7194ac..c5cc67f801d437ed8601ac67e37ccdc77f0cac0b:/main.c

diff --git a/main.c b/main.c
index 0ab2aad9..b28b285b 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,7 @@
-/*	$Id: main.c,v 1.58 2010/01/01 17:14:27 kristaps Exp $ */
+/*	$Id: main.c,v 1.100 2010/07/25 11:44:31 kristaps Exp $ */
 /*
- * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
+ * Copyright (c) 2008, 2009, 2010 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
@@ -18,9 +19,11 @@
 #include "config.h"
 #endif
 
+#include <sys/mman.h>
 #include <sys/stat.h>
 
 #include <assert.h>
+#include <ctype.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdint.h>
@@ -28,9 +31,11 @@
 #include <string.h>
 #include <unistd.h>
 
+#include "mandoc.h"
+#include "main.h"
 #include "mdoc.h"
 #include "man.h"
-#include "main.h"
+#include "roff.h"
 
 #define	UNCONST(a)	((void *)(uintptr_t)(const void *)(a))
 
@@ -61,59 +66,142 @@ enum	outt {
 	OUTT_ASCII = 0,
 	OUTT_TREE,
 	OUTT_HTML,
-	OUTT_LINT
+	OUTT_XHTML,
+	OUTT_LINT,
+	OUTT_PS,
+	OUTT_PDF
 };
 
 struct	curparse {
 	const char	 *file;		/* Current parse. */
 	int		  fd;		/* Current parse. */
 	int		  wflags;
+	/* FIXME: set by max error */
 #define	WARN_WALL	 (1 << 0)	/* All-warnings mask. */
 #define	WARN_WERR	 (1 << 2)	/* Warnings->errors. */
 	int		  fflags;
-#define	IGN_SCOPE	 (1 << 0) 	/* Ignore scope errors. */
-#define	NO_IGN_ESCAPE	 (1 << 1) 	/* Don't ignore bad escapes. */
-#define	NO_IGN_MACRO	 (1 << 2) 	/* Don't ignore bad macros. */
-#define	NO_IGN_CHARS	 (1 << 3)	/* Don't ignore bad chars. */
-#define	IGN_ERRORS	 (1 << 4)	/* Ignore failed parse. */
-	enum intt	  inttype;	/* Input parsers... */
-	struct man	 *man;
-	struct man	 *lastman;
-	struct mdoc	 *mdoc;
-	struct mdoc	 *lastmdoc;
-	enum outt	  outtype;	/* Output devices... */
-	out_mdoc	  outmdoc;
-	out_man	  	  outman;
-	out_free	  outfree;
-	void		 *outdata;
-	char		  outopts[BUFSIZ];
+#define	FL_IGN_SCOPE	 (1 << 0) 	/* Ignore scope errors. */
+#define	FL_NIGN_ESCAPE	 (1 << 1) 	/* Don't ignore bad escapes. */
+#define	FL_NIGN_MACRO	 (1 << 2) 	/* Don't ignore bad macros. */
+#define	FL_IGN_ERRORS	 (1 << 4)	/* Ignore failed parse. */
+#define	FL_STRICT	  FL_NIGN_ESCAPE | \
+			  FL_NIGN_MACRO /* ignore nothing */
+	enum intt	  inttype;	/* which parser to use */
+	struct man	 *man;		/* man parser */
+	struct mdoc	 *mdoc;		/* mdoc parser */
+	struct roff	 *roff;		/* roff parser (!NULL) */
+	struct regset	  regs;		/* roff registers */
+	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	const char * const	mandocerrs[MANDOCERR_MAX] = {
+	"ok",
+
+	"generic warning",
+
+	"text should be uppercase",
+	"sections out of conventional order",
+	"section name repeats",
+	"out of order prologue",
+	"repeated prologue entry",
+	"list type must come first",
+	"bad standard",
+	"bad library",
+	"tab in non-literal context",
+	"bad escape sequence",
+	"unterminated quoted string",
+	"argument requires the width argument",
+	"superfluous width argument",
+	"ignoring argument",
+	"bad date argument",
+	"bad width argument",
+	"unknown manual section",
+	"section not in conventional manual section",
+	"end of line whitespace",
+	"blocks badly nested",
+	"scope open on exit",
+
+	"generic error",
+
+	"NAME section must come first",
+	"bad Boolean value",
+	"child violates parent syntax",
+	"bad AT&T symbol",
+	"list type repeated",
+	"display type repeated",
+	"argument repeated",
+	"manual name not yet set",
+	"obsolete macro ignored",
+	"empty macro ignored",
+	"macro not allowed in body",
+	"macro not allowed in prologue",
+	"bad character",
+	"bad NAME section contents",
+	"no blank lines",
+	"no text in this context",
+	"bad comment style",
+	"unknown macro will be lost",
+	"line scope broken",
+	"argument count wrong",
+	"request scope close w/none open",
+	"scope already open",
+	"macro requires line argument(s)",
+	"macro requires body argument(s)",
+	"macro requires argument(s)",
+	"no title in document",
+	"missing list type",
+	"missing display type",
+	"missing font type",
+	"line argument(s) will be lost",
+	"body argument(s) will be lost",
+
+	"generic fatal error",
+
+	"column syntax is inconsistent",
+	"displays may not be nested",
+	"unsupported display type",
+	"blocks badly nested",
+	"no such block is open",
+	"scope broken, syntax violated",
+	"line scope broken, syntax violated",
+	"argument count wrong, violates syntax",
+	"child violates parent syntax",
+	"argument count wrong, violates syntax",
+	"no document body",
+	"no document prologue",
+	"utsname system call failed",
+	"memory exhausted",
+};
+
+static	void		  fdesc(struct curparse *);
+static	void		  ffile(const char *, struct curparse *);
 static	int		  foptions(int *, char *);
-static	int		  toptions(enum outt *, char *);
+static	struct man	 *man_init(struct curparse *);
+static	struct mdoc	 *mdoc_init(struct curparse *);
+static	struct roff	 *roff_init(struct curparse *);
 static	int		  moptions(enum intt *, char *);
-static	int		  woptions(int *, char *);
-static	int		  merr(void *, int, int, const char *);
-static	int		  mwarn(void *, int, int, const char *);
-static	int		  ffile(struct buf *, struct buf *, 
-				const char *, struct curparse *);
-static	int		  fdesc(struct buf *, struct buf *,
-				struct curparse *);
+static	int		  mmsg(enum mandocerr, void *, 
+				int, int, const char *);
 static	int		  pset(const char *, int, struct curparse *,
 				struct man **, struct mdoc **);
-static	struct man	 *man_init(struct curparse *);
-static	struct mdoc	 *mdoc_init(struct curparse *);
-static	void		  version(void) __attribute__((noreturn));
+static	int		  toptions(struct curparse *, char *);
 static	void		  usage(void) __attribute__((noreturn));
+static	void		  version(void) __attribute__((noreturn));
+static	int		  woptions(int *, char *);
 
 static	const char	 *progname;
-
+static	int		  with_fatal;
+static	int		  with_error;
 
 int
 main(int argc, char *argv[])
 {
-	int		 c, rc;
-	struct buf	 ln, blk;
+	int		 c;
 	struct curparse	 curp;
 
 	progname = strrchr(argv[0], '/');
@@ -143,7 +231,7 @@ main(int argc, char *argv[])
 			(void)strlcat(curp.outopts, ",", BUFSIZ);
 			break;
 		case ('T'):
-			if ( ! toptions(&curp.outtype, optarg))
+			if ( ! toptions(&curp, optarg))
 				return(EXIT_FAILURE);
 			break;
 		case ('W'):
@@ -161,52 +249,32 @@ main(int argc, char *argv[])
 	argc -= optind;
 	argv += optind;
 
-	memset(&ln, 0, sizeof(struct buf));
-	memset(&blk, 0, sizeof(struct buf));
-
-	rc = 1;
-
 	if (NULL == *argv) {
 		curp.file = "<stdin>";
 		curp.fd = STDIN_FILENO;
 
-		c = fdesc(&blk, &ln, &curp);
-		if ( ! (IGN_ERRORS & curp.fflags)) 
-			rc = 1 == c ? 1 : 0;
-		else
-			rc = -1 == c ? 0 : 1;
+		fdesc(&curp);
 	}
 
-	while (rc && *argv) {
-		c = ffile(&blk, &ln, *argv, &curp);
-		if ( ! (IGN_ERRORS & curp.fflags)) 
-			rc = 1 == c ? 1 : 0;
-		else
-			rc = -1 == c ? 0 : 1;
-
-		argv++;
-		if (*argv && rc) {
-			if (curp.lastman)
-				man_reset(curp.lastman);
-			if (curp.lastmdoc)
-				mdoc_reset(curp.lastmdoc);
-			curp.lastman = NULL;
-			curp.lastmdoc = NULL;
-		}
+	while (*argv) {
+		ffile(*argv, &curp);
+
+		if (with_fatal && !(curp.fflags & FL_IGN_ERRORS))
+			break;
+		++argv;
 	}
 
-	if (blk.buf)
-		free(blk.buf);
-	if (ln.buf)
-		free(ln.buf);
 	if (curp.outfree)
 		(*curp.outfree)(curp.outdata);
 	if (curp.mdoc)
 		mdoc_free(curp.mdoc);
 	if (curp.man)
 		man_free(curp.man);
+	if (curp.roff)
+		roff_free(curp.roff);
 
-	return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
+	return((with_fatal || with_error) ? 
+			EXIT_FAILURE :  EXIT_SUCCESS);
 }
 
 
@@ -223,9 +291,9 @@ static void
 usage(void)
 {
 
-	(void)fprintf(stderr, "usage: %s [-V] [-foption...] "
+	(void)fprintf(stderr, "usage: %s [-V] [-foption] "
 			"[-mformat] [-Ooption] [-Toutput] "
-			"[-Werr...]\n", progname);
+			"[-Werr] [file...]\n", progname);
 	exit(EXIT_FAILURE);
 }
 
@@ -234,23 +302,25 @@ static struct man *
 man_init(struct curparse *curp)
 {
 	int		 pflags;
-	struct man_cb	 mancb;
-
-	mancb.man_err = merr;
-	mancb.man_warn = mwarn;
 
 	/* Defaults from mandoc.1. */
 
-	pflags = MAN_IGN_MACRO | MAN_IGN_ESCAPE | MAN_IGN_CHARS;
+	pflags = MAN_IGN_MACRO | MAN_IGN_ESCAPE;
 
-	if (curp->fflags & NO_IGN_MACRO)
+	if (curp->fflags & FL_NIGN_MACRO)
 		pflags &= ~MAN_IGN_MACRO;
-	if (curp->fflags & NO_IGN_CHARS)
-		pflags &= ~MAN_IGN_CHARS;
-	if (curp->fflags & NO_IGN_ESCAPE)
+	if (curp->fflags & FL_NIGN_ESCAPE)
 		pflags &= ~MAN_IGN_ESCAPE;
 
-	return(man_alloc(curp, pflags, &mancb));
+	return(man_alloc(&curp->regs, curp, pflags, mmsg));
+}
+
+
+static struct roff *
+roff_init(struct curparse *curp)
+{
+
+	return(roff_alloc(&curp->regs, mmsg, curp));
 }
 
 
@@ -258,178 +328,325 @@ static struct mdoc *
 mdoc_init(struct curparse *curp)
 {
 	int		 pflags;
-	struct mdoc_cb	 mdoccb;
-
-	mdoccb.mdoc_err = merr;
-	mdoccb.mdoc_warn = mwarn;
 
 	/* Defaults from mandoc.1. */
 
-	pflags = MDOC_IGN_MACRO | MDOC_IGN_ESCAPE | MDOC_IGN_CHARS;
+	pflags = MDOC_IGN_MACRO | MDOC_IGN_ESCAPE;
 
-	if (curp->fflags & IGN_SCOPE)
+	if (curp->fflags & FL_IGN_SCOPE)
 		pflags |= MDOC_IGN_SCOPE;
-	if (curp->fflags & NO_IGN_ESCAPE)
+	if (curp->fflags & FL_NIGN_ESCAPE)
 		pflags &= ~MDOC_IGN_ESCAPE;
-	if (curp->fflags & NO_IGN_MACRO)
+	if (curp->fflags & FL_NIGN_MACRO)
 		pflags &= ~MDOC_IGN_MACRO;
-	if (curp->fflags & NO_IGN_CHARS)
-		pflags &= ~MDOC_IGN_CHARS;
 
-	return(mdoc_alloc(curp, pflags, &mdoccb));
+	return(mdoc_alloc(&curp->regs, curp, pflags, mmsg));
 }
 
 
-static int
-ffile(struct buf *blk, struct buf *ln, 
-		const char *file, struct curparse *curp)
+static void
+ffile(const char *file, struct curparse *curp)
 {
-	int		 c;
 
 	curp->file = file;
 	if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) {
 		perror(curp->file);
-		return(-1);
+		with_fatal = 1;
+		return;
 	}
 
-	c = fdesc(blk, ln, curp);
+	fdesc(curp);
 
 	if (-1 == close(curp->fd))
 		perror(curp->file);
+}
+
 
-	return(c);
+static int
+resize_buf(struct buf *buf, size_t initial)
+{
+	void *tmp;
+	size_t sz;
+
+	if (buf->sz == 0)
+		sz = initial;
+	else
+		sz = 2 * buf->sz;
+	tmp = realloc(buf->buf, sz);
+	if (NULL == tmp) {
+		perror(NULL);
+		return(0);
+	}
+	buf->buf = tmp;
+	buf->sz = sz;
+	return(1);
 }
 
 
 static int
-fdesc(struct buf *blk, struct buf *ln, struct curparse *curp)
+read_whole_file(struct curparse *curp, struct buf *fb, int *with_mmap)
 {
-	size_t		 sz;
-	ssize_t		 ssz;
 	struct stat	 st;
-	int		 j, i, pos, lnn, comment;
-	struct man	*man;
-	struct mdoc	*mdoc;
+	size_t		 off;
+	ssize_t		 ssz;
 
-	sz = BUFSIZ;
-	man = NULL;
-	mdoc = NULL;
+	if (-1 == fstat(curp->fd, &st)) {
+		perror(curp->file);
+		with_fatal = 1;
+		return(0);
+	}
 
 	/*
-	 * Two buffers: ln and buf.  buf is the input buffer optimised
-	 * here for each file's block size.  ln is a line buffer.  Both
-	 * growable, hence passed in by ptr-ptr.
+	 * 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 (-1 == fstat(curp->fd, &st))
-		perror(curp->file);
-	else if ((size_t)st.st_blksize > sz)
-		sz = st.st_blksize;
-
-	if (sz > blk->sz) {
-		blk->buf = realloc(blk->buf, sz);
-		if (NULL == blk->buf) {
-			perror(NULL);
-			exit(EXIT_FAILURE);
+	if (S_ISREG(st.st_mode)) {
+		if (st.st_size >= (1U << 31)) {
+			fprintf(stderr, "%s: input too large\n", 
+					curp->file);
+			with_fatal = 1;
+			return(0);
 		}
-		blk->sz = sz;
+		*with_mmap = 1;
+		fb->sz = (size_t)st.st_size;
+		fb->buf = mmap(NULL, fb->sz, PROT_READ, 
+				MAP_FILE|MAP_SHARED, curp->fd, 0);
+		if (fb->buf != MAP_FAILED)
+			return(1);
 	}
 
-	/* Fill buf with file blocksize. */
+	/*
+	 * 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.
+	 */
 
-	for (lnn = pos = comment = 0; ; ) {
-		if (-1 == (ssz = read(curp->fd, blk->buf, sz))) {
+	*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", 
+						curp->file);
+				break;
+			}
+			if (! resize_buf(fb, 65536))
+				break;
+		}
+		ssz = read(curp->fd, fb->buf + (int)off, fb->sz - off);
+		if (ssz == 0) {
+			fb->sz = off;
+			return(1);
+		}
+		if (ssz == -1) {
 			perror(curp->file);
-			return(-1);
-		} else if (0 == ssz) 
 			break;
+		}
+		off += (size_t)ssz;
+	}
 
-		/* Parse the read block into partial or full lines. */
+	free(fb->buf);
+	fb->buf = NULL;
+	with_fatal = 1;
+	return(0);
+}
 
-		for (i = 0; i < (int)ssz; i++) {
-			if (pos >= (int)ln->sz) {
-				ln->sz += 256; /* Step-size. */
-				ln->buf = realloc(ln->buf, ln->sz);
-				if (NULL == ln->buf) {
-					perror(NULL);
-					return(EXIT_FAILURE);
-				}
-			}
 
-			if ('\n' != blk->buf[i]) {
-				if (comment)
-					continue;
-				ln->buf[pos++] = blk->buf[i];
+static void
+fdesc(struct curparse *curp)
+{
+	struct buf	 ln, blk;
+	int		 i, pos, lnn, lnn_start, with_mmap, of;
+	enum rofferr	 re;
+	struct man	*man;
+	struct mdoc	*mdoc;
+	struct roff	*roff;
 
-				/* Handle in-line `\"' comments. */
+	man = NULL;
+	mdoc = NULL;
+	roff = NULL;
 
-				if (1 == pos || '\"' != ln->buf[pos - 1])
-					continue;
+	memset(&ln, 0, sizeof(struct buf));
 
-				for (j = pos - 2; j >= 0; j--)
-					if ('\\' != ln->buf[j])
-						break;
+	/*
+	 * Two buffers: ln and buf.  buf is the input file and may be
+	 * memory mapped.  ln is a line buffer and grows on-demand.
+	 */
 
-				if ( ! ((pos - 2 - j) % 2))
-					continue;
+	if ( ! read_whole_file(curp, &blk, &with_mmap))
+		return;
+
+	if (NULL == curp->roff) 
+		curp->roff = roff_init(curp);
+	if (NULL == (roff = curp->roff))
+		goto bailout;
+
+	for (i = 0, lnn = 1; i < (int)blk.sz;) {
+		pos = 0;
+		lnn_start = lnn;
+		while (i < (int)blk.sz) {
+			if ('\n' == blk.buf[i]) {
+				++i;
+				++lnn;
+				break;
+			}
 
-				comment = 1;
-				pos -= 2;
+			/* 
+			 * 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 drop these characters so
+			 * we don't display gibberish.  Note to manual
+			 * writers: use special characters.
+			 */
+
+			if ( ! isgraph((u_char)blk.buf[i]) &&
+					! isblank((u_char)blk.buf[i])) {
+				if ( ! mmsg(MANDOCERR_BADCHAR, curp, 
+						lnn_start, pos, 
+						"ignoring byte"))
+					goto bailout;
+				i++;
 				continue;
-			} 
-
-			/* Handle escaped `\\n' newlines. */
+			}
 
-			if (pos > 0 && 0 == comment && 
-					'\\' == ln->buf[pos - 1]) {
-				for (j = pos - 1; j >= 0; j--)
-					if ('\\' != ln->buf[j])
+			/* Trailing backslash is like a plain character. */
+			if ('\\' != blk.buf[i] || i + 1 == (int)blk.sz) {
+				if (pos >= (int)ln.sz)
+					if (! resize_buf(&ln, 256))
+						goto bailout;
+				ln.buf[pos++] = blk.buf[i++];
+				continue;
+			}
+			/* Found an escape and at least one other character. */
+			if ('\n' == blk.buf[i + 1]) {
+				/* Escaped newlines are skipped over */
+				i += 2;
+				++lnn;
+				continue;
+			}
+			if ('"' == 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;
-				if ( ! ((pos - j) % 2)) {
-					pos--;
-					lnn++;
-					continue;
 				}
+				break;
 			}
+			/* Some other escape sequence, copy and continue. */
+			if (pos + 1 >= (int)ln.sz)
+				if (! resize_buf(&ln, 256))
+					goto bailout;
 
-			ln->buf[pos] = 0;
-			lnn++;
-
-			/* If unset, assign parser in pset(). */
-
-			if ( ! (man || mdoc) && ! pset(ln->buf, 
-						pos, curp, &man, &mdoc))
-				return(-1);
-
-			pos = comment = 0;
-
-			/* Pass down into parsers. */
-
-			if (man && ! man_parseln(man, lnn, ln->buf))
-				return(0);
-			if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf))
-				return(0);
+			ln.buf[pos++] = blk.buf[i++];
+			ln.buf[pos++] = blk.buf[i++];
 		}
+
+ 		if (pos >= (int)ln.sz)
+			if (! resize_buf(&ln, 256))
+				goto bailout;
+		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;
+		do {
+			re = roff_parseln(roff, lnn_start, 
+					&ln.buf, &ln.sz, of, &of);
+		} while (ROFF_RERUN == re);
+
+		if (ROFF_IGN == re)
+			continue;
+		else if (ROFF_ERR == re)
+			goto bailout;
+
+		/*
+		 * If input parsers have not been allocated, do so now.
+		 * We keep these instanced betwen parsers, but set them
+		 * locally per parse routine since we can use different
+		 * parsers with each one.
+		 */
+
+		if ( ! (man || mdoc))
+			if ( ! pset(ln.buf + of, pos - of, curp, &man, &mdoc))
+				goto bailout;
+
+		/* Lastly, push down into the parsers themselves. */
+
+		if (man && ! man_parseln(man, lnn_start, ln.buf, of))
+			goto bailout;
+		if (mdoc && ! mdoc_parseln(mdoc, lnn_start, ln.buf, of))
+			goto bailout;
 	}
 
 	/* NOTE a parser may not have been assigned, yet. */
 
 	if ( ! (man || mdoc)) {
 		fprintf(stderr, "%s: Not a manual\n", curp->file);
-		return(0);
+		goto bailout;
 	}
 
+	/* Clean up the parse routine ASTs. */
+
 	if (mdoc && ! mdoc_endparse(mdoc))
-		return(0);
+		goto bailout;
 	if (man && ! man_endparse(man))
-		return(0);
+		goto bailout;
+	if (roff && ! roff_endparse(roff))
+		goto bailout;
 
 	/* 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);
+			break;
 		case (OUTT_HTML):
 			curp->outdata = html_alloc(curp->outopts);
+			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;
 			curp->outfree = html_free;
@@ -438,13 +655,15 @@ fdesc(struct buf *blk, struct buf *ln, struct curparse *curp)
 			curp->outman = tree_man;
 			curp->outmdoc = tree_mdoc;
 			break;
-		case (OUTT_LINT):
-			break;
-		default:
-			curp->outdata = ascii_alloc();
+		case (OUTT_PDF):
+			/* FALLTHROUGH */
+		case (OUTT_ASCII):
+			/* FALLTHROUGH */
+		case (OUTT_PS):
 			curp->outman = terminal_man;
 			curp->outmdoc = terminal_mdoc;
-			curp->outfree = terminal_free;
+			break;
+		default:
 			break;
 		}
 	}
@@ -456,7 +675,26 @@ fdesc(struct buf *blk, struct buf *ln, struct curparse *curp)
 	if (mdoc && curp->outmdoc)
 		(*curp->outmdoc)(curp->outdata, mdoc);
 
-	return(1);
+ cleanup:
+	memset(&curp->regs, 0, sizeof(struct regset));
+	if (mdoc)
+		mdoc_reset(mdoc);
+	if (man)
+		man_reset(man);
+	if (roff)
+		roff_reset(roff);
+	if (ln.buf)
+		free(ln.buf);
+	if (with_mmap)
+		munmap(blk.buf, blk.sz);
+	else
+		free(blk.buf);
+
+	return;
+
+ bailout:
+	with_fatal = 1;
+	goto cleanup;
 }
 
 
@@ -474,7 +712,7 @@ pset(const char *buf, int pos, struct curparse *curp,
 	 * default to -man, which is more lenient.
 	 */
 
-	if (buf[0] == '.') {
+	if ('.' == buf[0] || '\'' == buf[0]) {
 		for (i = 1; buf[i]; i++)
 			if (' ' != buf[i] && '\t' != buf[i])
 				break;
@@ -488,14 +726,12 @@ pset(const char *buf, int pos, struct curparse *curp,
 			curp->mdoc = mdoc_init(curp);
 		if (NULL == (*mdoc = curp->mdoc))
 			return(0);
-		curp->lastmdoc = *mdoc;
 		return(1);
 	case (INTT_MAN):
 		if (NULL == curp->man) 
 			curp->man = man_init(curp);
 		if (NULL == (*man = curp->man))
 			return(0);
-		curp->lastman = *man;
 		return(1);
 	default:
 		break;
@@ -506,7 +742,6 @@ pset(const char *buf, int pos, struct curparse *curp,
 			curp->mdoc = mdoc_init(curp);
 		if (NULL == (*mdoc = curp->mdoc))
 			return(0);
-		curp->lastmdoc = *mdoc;
 		return(1);
 	} 
 
@@ -514,7 +749,6 @@ pset(const char *buf, int pos, struct curparse *curp,
 		curp->man = man_init(curp);
 	if (NULL == (*man = curp->man))
 		return(0);
-	curp->lastman = *man;
 	return(1);
 }
 
@@ -539,17 +773,26 @@ moptions(enum intt *tflags, char *arg)
 
 
 static int
-toptions(enum outt *tflags, char *arg)
+toptions(struct curparse *curp, char *arg)
 {
 
 	if (0 == strcmp(arg, "ascii"))
-		*tflags = OUTT_ASCII;
-	else if (0 == strcmp(arg, "lint"))
-		*tflags = OUTT_LINT;
+		curp->outtype = OUTT_ASCII;
+	else if (0 == strcmp(arg, "lint")) {
+		curp->outtype = OUTT_LINT;
+		curp->wflags |= WARN_WALL;
+		curp->fflags |= FL_STRICT;
+	}
 	else if (0 == strcmp(arg, "tree"))
-		*tflags = OUTT_TREE;
+		curp->outtype = OUTT_TREE;
 	else if (0 == strcmp(arg, "html"))
-		*tflags = OUTT_HTML;
+		curp->outtype = OUTT_HTML;
+	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);
@@ -568,36 +811,31 @@ foptions(int *fflags, char *arg)
 	toks[0] = "ign-scope";
 	toks[1] = "no-ign-escape";
 	toks[2] = "no-ign-macro";
-	toks[3] = "no-ign-chars";
-	toks[4] = "ign-errors";
-	toks[5] = "strict";
-	toks[6] = "ign-escape";
-	toks[7] = NULL;
+	toks[3] = "ign-errors";
+	toks[4] = "strict";
+	toks[5] = "ign-escape";
+	toks[6] = NULL;
 
 	while (*arg) {
 		o = arg;
 		switch (getsubopt(&arg, UNCONST(toks), &v)) {
 		case (0):
-			*fflags |= IGN_SCOPE;
+			*fflags |= FL_IGN_SCOPE;
 			break;
 		case (1):
-			*fflags |= NO_IGN_ESCAPE;
+			*fflags |= FL_NIGN_ESCAPE;
 			break;
 		case (2):
-			*fflags |= NO_IGN_MACRO;
+			*fflags |= FL_NIGN_MACRO;
 			break;
 		case (3):
-			*fflags |= NO_IGN_CHARS;
+			*fflags |= FL_IGN_ERRORS;
 			break;
 		case (4):
-			*fflags |= IGN_ERRORS;
+			*fflags |= FL_STRICT;
 			break;
 		case (5):
-			*fflags |= NO_IGN_ESCAPE | 
-			 	   NO_IGN_MACRO | NO_IGN_CHARS;
-			break;
-		case (6):
-			*fflags &= ~NO_IGN_ESCAPE;
+			*fflags &= ~FL_NIGN_ESCAPE;
 			break;
 		default:
 			fprintf(stderr, "%s: Bad argument\n", o);
@@ -638,37 +876,41 @@ woptions(int *wflags, char *arg)
 }
 
 
-/* ARGSUSED */
 static int
-merr(void *arg, int line, int col, const char *msg)
+mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg)
 {
-	struct curparse *curp;
+	struct curparse *cp;
+	const char *level;
+	int rc;
 
-	curp = (struct curparse *)arg;
-
-	(void)fprintf(stderr, "%s:%d:%d: error: %s\n", 
-			curp->file, line, col + 1, msg);
-
-	return(0);
-}
-
-
-static int
-mwarn(void *arg, int line, int col, const char *msg)
-{
-	struct curparse *curp;
-
-	curp = (struct curparse *)arg;
+	cp = (struct curparse *)arg;
+	level = NULL;
+	rc = 1;
 
-	if ( ! (curp->wflags & WARN_WALL))
-		return(1);
+	if (t >= MANDOCERR_FATAL) {
+		with_fatal = 1;
+		level = "FATAL";
+		rc = 0;
+	} else {
+		if ( ! (WARN_WALL & cp->wflags))
+			return(1);
+		if (t >= MANDOCERR_ERROR) {
+			with_error = 1;
+			level = "ERROR";
+		}
+		if (WARN_WERR & cp->wflags) {
+			with_fatal = 1;
+			rc = 0;
+		}
+	}
 
-	(void)fprintf(stderr, "%s:%d:%d: warning: %s\n", 
-			curp->file, line, col + 1, msg);
+	fprintf(stderr, "%s:%d:%d:", cp->file, ln, col + 1);
+	if (level)
+		fprintf(stderr, " %s:", level);
+	fprintf(stderr, " %s", mandocerrs[t]);
+	if (msg)
+		fprintf(stderr, ": %s", msg);
+	fputc('\n', stderr);
 
-	if ( ! (curp->wflags & WARN_WERR))
-		return(1);
-	
-	return(0);
+	return(rc);
 }
-