X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/223727dbba0bff013b3bacc8607570b2b41667c1..40384b46245ed46f132d087f7569bf00117bbf9a:/main.c

diff --git a/main.c b/main.c
index 044ec79f..2be68a93 100644
--- a/main.c
+++ b/main.c
@@ -1,6 +1,6 @@
-/*	$Id: main.c,v 1.104 2010/08/20 08:13:43 schwarze Exp $ */
+/*	$Id: main.c,v 1.135 2011/01/04 15:02:00 kristaps Exp $ */
 /*
- * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
+ * 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
@@ -41,6 +41,7 @@
 #define	MAP_FILE	0
 #endif
 
+#define	REPARSE_LIMIT	1000
 #define	UNCONST(a)	((void *)(uintptr_t)(const void *)(a))
 
 /* FIXME: Intel's compiler?  LLVM?  pcc?  */
@@ -79,13 +80,17 @@ enum	outt {
 struct	curparse {
 	const char	 *file;		/* Current parse. */
 	int		  fd;		/* Current parse. */
+	int		  line;		/* Line number in the file. */
 	enum mandoclevel  wlevel;	/* Ignore messages below this. */
 	int		  wstop;	/* Stop after a file with a warning. */
 	enum intt	  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) */
 	struct regset	  regs;		/* roff registers */
+	int		  reparse_count; /* finite interpolation stack */
 	enum outt	  outtype; 	/* which output to use */
 	out_mdoc	  outmdoc;	/* mdoc output ptr */
 	out_man	  	  outman;	/* man output ptr */
@@ -119,93 +124,122 @@ static	const char * const	mandocerrs[MANDOCERR_MAX] = {
 
 	"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",
+	/* related to the prologue */
+	"no title in document",
+	"document title should be all caps",
 	"unknown manual section",
+	"cannot parse date argument",
+	"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",
-	"end of line whitespace",
+
+	/* related to macros and nesting */
+	"skipping obsolete macro",
+	"skipping paragraph macro",
 	"blocks badly nested",
-	"scope open on exit",
+	"child violates parent syntax",
+	"nested displays are not portable",
+	"already in literal mode",
 
-	"generic error",
+	/* 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",
 
-	"NAME section must come first",
+	/* related to bad macro arguments */
+	"skipping argument",
+	"duplicate argument",
+	"duplicate display type",
+	"duplicate list type",
+	"unknown AT&T UNIX version",
 	"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",
+	"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",
-	"unknown macro will be lost",
+	"unknown escape sequence",
+	"unterminated quoted string",
+	
+	/* related to tables */
+	"extra data cells",
+
+	"generic error",
+
+	/* related to tables */
+	"bad table syntax",
+	"bad table option",
+	"bad table layout",
+	"no table layout cells specified",
+	"no table data cells specified",
+	"ignore data in cell",
+	"data block still open",
+
+	"input stack limit exceeded, infinite loop?",
+	"skipping bad character",
+	"skipping text before the first section header",
+	"skipping unknown macro",
+	"NOT IMPLEMENTED: skipping request",
 	"line scope broken",
 	"argument count wrong",
-	"request scope close w/none open",
-	"scope already open",
+	"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)",
-	"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",
+	"NOT IMPLEMENTED: .Bd -file",
 	"line scope broken, syntax violated",
 	"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",
-	"utsname system call failed",
 	"static buffer exhausted",
 };
 
+static	void		  parsebuf(struct curparse *, struct buf, int);
+static	void		  pdesc(struct curparse *);
 static	void		  fdesc(struct curparse *);
 static	void		  ffile(const char *, struct curparse *);
+static	int		  pfile(const char *, struct curparse *);
 static	int		  moptions(enum intt *, char *);
 static	int		  mmsg(enum mandocerr, void *, 
 				int, int, const char *);
-static	void		  pset(const char *, int, struct curparse *,
-				struct man **, struct mdoc **);
+static	void		  pset(const char *, int, struct curparse *);
 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;
+static	enum mandoclevel  file_status = MANDOCLEVEL_OK;
 static	enum mandoclevel  exit_status = MANDOCLEVEL_OK;
 
 int
@@ -231,7 +265,7 @@ main(int argc, char *argv[])
 		switch (c) {
 		case ('m'):
 			if ( ! moptions(&curp.inttype, optarg))
-				return(MANDOCLEVEL_BADARG);
+				return((int)MANDOCLEVEL_BADARG);
 			break;
 		case ('O'):
 			(void)strlcat(curp.outopts, optarg, BUFSIZ);
@@ -239,11 +273,11 @@ main(int argc, char *argv[])
 			break;
 		case ('T'):
 			if ( ! toptions(&curp, optarg))
-				return(MANDOCLEVEL_BADARG);
+				return((int)MANDOCLEVEL_BADARG);
 			break;
 		case ('W'):
 			if ( ! woptions(&curp, optarg))
-				return(MANDOCLEVEL_BADARG);
+				return((int)MANDOCLEVEL_BADARG);
 			break;
 		case ('V'):
 			version();
@@ -272,14 +306,14 @@ main(int argc, char *argv[])
 
 	if (curp.outfree)
 		(*curp.outfree)(curp.outdata);
-	if (curp.mdoc)
-		mdoc_free(curp.mdoc);
-	if (curp.man)
-		man_free(curp.man);
+	if (curp.pmdoc)
+		mdoc_free(curp.pmdoc);
+	if (curp.pman)
+		man_free(curp.pman);
 	if (curp.roff)
 		roff_free(curp.roff);
 
-	return(exit_status);
+	return((int)exit_status);
 }
 
 
@@ -288,7 +322,7 @@ version(void)
 {
 
 	(void)printf("%s %s\n", progname, VERSION);
-	exit(MANDOCLEVEL_OK);
+	exit((int)MANDOCLEVEL_OK);
 }
 
 
@@ -296,18 +330,32 @@ static void
 usage(void)
 {
 
-	(void)fprintf(stderr, "usage: %s [-V] [-foption] "
-			"[-mformat] [-Ooption] [-Toutput] "
-			"[-Werr] [file...]\n", progname);
-	exit(MANDOCLEVEL_BADARG);
+	(void)fprintf(stderr, "usage: %s "
+			"[-V] "
+			"[-foption] "
+			"[-mformat] "
+			"[-Ooption] "
+			"[-Toutput] "
+			"[-Werr] "
+			"[file...]\n", 
+			progname);
+
+	exit((int)MANDOCLEVEL_BADARG);
 }
 
-
 static void
 ffile(const char *file, struct curparse *curp)
 {
 
+	/*
+	 * Called once per input file.  Get the file ready for reading,
+	 * pass it through to the parser-driver, then close it out.
+	 * XXX: don't do anything special as this is only called for
+	 * files; stdin goes directly to fdesc().
+	 */
+
 	curp->file = file;
+
 	if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) {
 		perror(curp->file);
 		exit_status = MANDOCLEVEL_SYSERR;
@@ -320,16 +368,45 @@ ffile(const char *file, struct curparse *curp)
 		perror(curp->file);
 }
 
+static int
+pfile(const char *file, struct curparse *curp)
+{
+	const char	*savefile;
+	int		 fd, savefd;
+
+	if (-1 == (fd = open(file, O_RDONLY, 0))) {
+		perror(file);
+		file_status = MANDOCLEVEL_SYSERR;
+		return(0);
+	}
+
+	savefile = curp->file;
+	savefd = curp->fd;
+
+	curp->file = file;
+	curp->fd = fd;
+
+	pdesc(curp);
+
+	curp->file = savefile;
+	curp->fd = savefd;
+
+	if (-1 == close(fd))
+		perror(file);
+
+	return(MANDOCLEVEL_FATAL > file_status ? 1 : 0);
+}
+
 
 static void
 resize_buf(struct buf *buf, size_t initial)
 {
 
-	buf->sz = buf->sz ? 2 * buf->sz : 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(MANDOCLEVEL_SYSERR);
+		exit((int)MANDOCLEVEL_SYSERR);
 	}
 }
 
@@ -406,39 +483,207 @@ read_whole_file(struct curparse *curp, struct buf *fb, int *with_mmap)
 static void
 fdesc(struct curparse *curp)
 {
-	struct buf	 ln, blk;
-	int		 i, pos, lnn, lnn_start, with_mmap, of;
-	enum rofferr	 re;
-	unsigned char	 c;
-	struct man	*man;
-	struct mdoc	*mdoc;
-	struct roff	*roff;
 
-	man = NULL;
-	mdoc = NULL;
-	roff = NULL;
+	/*
+	 * Called once per file with an opened file descriptor.  All
+	 * pre-file-parse operations (whether stdin or a file) should go
+	 * here.
+	 *
+	 * This calls down into the nested parser, which drills down and
+	 * fully parses a file and all its dependences (i.e., `so').  It
+	 * then runs the cleanup validators and pushes to output.
+	 */
+
+	/* Zero the parse type. */
 
-	memset(&ln, 0, sizeof(struct buf));
+	curp->mdoc = NULL;
+	curp->man = NULL;
+	file_status = MANDOCLEVEL_OK;
+
+	/* Make sure the mandotory roff parser is initialised. */
+
+	if (NULL == curp->roff) {
+		curp->roff = roff_alloc(&curp->regs, curp, mmsg);
+		assert(curp->roff);
+	}
+
+	/* Fully parse the file. */
+
+	pdesc(curp);
+
+	if (MANDOCLEVEL_FATAL <= file_status)
+		goto cleanup;
+
+	/* NOTE a parser may not have been assigned, yet. */
+
+	if ( ! (curp->man || curp->mdoc)) {
+		fprintf(stderr, "%s: Not a manual\n", curp->file);
+		file_status = MANDOCLEVEL_FATAL;
+		goto cleanup;
+	}
+
+	/* Clean up the parse routine ASTs. */
+
+	if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
+		assert(MANDOCLEVEL_FATAL <= file_status);
+		goto cleanup;
+	}
+
+	if (curp->man && ! man_endparse(curp->man)) {
+		assert(MANDOCLEVEL_FATAL <= file_status);
+		goto cleanup;
+	}
+
+	assert(curp->roff);
+	roff_endparse(curp->roff);
 
 	/*
-	 * Two buffers: ln and buf.  buf is the input file and may be
-	 * memory mapped.  ln is a line buffer and grows on-demand.
+	 * With -Wstop and warnings or errors of at least
+	 * the requested level, do not produce output.
+	 */
+
+	if (MANDOCLEVEL_OK != file_status && 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);
+			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;
+			break;
+		case (OUTT_TREE):
+			curp->outman = tree_man;
+			curp->outmdoc = tree_mdoc;
+			break;
+		case (OUTT_PDF):
+			/* FALLTHROUGH */
+		case (OUTT_ASCII):
+			/* FALLTHROUGH */
+		case (OUTT_PS):
+			curp->outman = terminal_man;
+			curp->outmdoc = terminal_mdoc;
+			break;
+		default:
+			break;
+		}
+	}
+
+	/* Execute the out device, if it exists. */
+
+	if (curp->man && curp->outman)
+		(*curp->outman)(curp->outdata, curp->man);
+	if (curp->mdoc && curp->outmdoc)
+		(*curp->outmdoc)(curp->outdata, curp->mdoc);
+
+ cleanup:
+
+	memset(&curp->regs, 0, sizeof(struct regset));
+
+	/* Reset the current-parse compilers. */
+
+	if (curp->mdoc)
+		mdoc_reset(curp->mdoc);
+	if (curp->man)
+		man_reset(curp->man);
+
+	assert(curp->roff);
+	roff_reset(curp->roff);
+
+	if (exit_status < file_status)
+		exit_status = file_status;
+
+	return;
+}
+
+static void
+pdesc(struct curparse *curp)
+{
+	struct buf	 blk;
+	int		 with_mmap;
+
+	/*
+	 * 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(curp, &blk, &with_mmap)) {
-		exit_status = MANDOCLEVEL_SYSERR;
+		file_status = MANDOCLEVEL_SYSERR;
 		return;
 	}
 
-	if (NULL == curp->roff) 
-		curp->roff = roff_alloc(&curp->regs, curp, mmsg);
-	assert(curp->roff);
-	roff = curp->roff;
+	/* Line number is per-file. */
 
-	for (i = 0, lnn = 1; i < (int)blk.sz;) {
-		pos = 0;
-		lnn_start = lnn;
-		while (i < (int)blk.sz) {
+	curp->line = 1;
+
+	parsebuf(curp, blk, 1);
+
+	if (with_mmap)
+		munmap(blk.buf, blk.sz);
+	else
+		free(blk.buf);
+}
+
+static void
+parsebuf(struct curparse *curp, struct buf blk, int start)
+{
+	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;
+
+	/*
+	 * 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').
+	 */
+
+	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])) {
 			if ('\n' == blk.buf[i]) {
 				++i;
 				++lnn;
@@ -455,27 +700,33 @@ fdesc(struct curparse *curp)
 			 */
 
 			c = (unsigned char) blk.buf[i];
-			if ( ! (isascii(c) && (isgraph(c) || isblank(c)))) {
+
+			if ( ! (isascii(c) && 
+					(isgraph(c) || isblank(c)))) {
 				mmsg(MANDOCERR_BADCHAR, curp, 
-				    lnn_start, pos, "ignoring byte");
+				    curp->line, pos, "ignoring byte");
 				i++;
 				continue;
 			}
 
-			/* Trailing backslash is like a plain character. */
+			/* 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 an escape and at least one other character. */
+
+			/* Found escape & at least one other char. */
+
 			if ('\n' == blk.buf[i + 1]) {
-				/* Escaped newlines are skipped over */
 				i += 2;
+				/* Escaped newlines are skipped over */
 				++lnn;
 				continue;
 			}
+
 			if ('"' == blk.buf[i + 1]) {
 				i += 2;
 				/* Comment, skip to end of line */
@@ -486,6 +737,7 @@ fdesc(struct curparse *curp)
 						break;
 					}
 				}
+
 				/* Backout trailing whitespaces */
 				for (; pos > 0; --pos) {
 					if (ln.buf[pos - 1] != ' ')
@@ -495,7 +747,9 @@ fdesc(struct curparse *curp)
 				}
 				break;
 			}
-			/* Some other escape sequence, copy and continue. */
+
+			/* Some other escape sequence, copy & cont. */
+
 			if (pos + 1 >= (int)ln.sz)
 				resize_buf(&ln, 256);
 
@@ -505,6 +759,7 @@ fdesc(struct curparse *curp)
 
  		if (pos >= (int)ln.sz)
 			resize_buf(&ln, 256);
+
 		ln.buf[pos] = '\0';
 
 		/*
@@ -517,18 +772,50 @@ fdesc(struct curparse *curp)
 		 */
 
 		of = 0;
-		do {
-			re = roff_parseln(roff, lnn_start, 
-					&ln.buf, &ln.sz, of, &of);
-		} while (ROFF_RERUN == re);
 
-		if (ROFF_IGN == re) {
+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)
+				parsebuf(curp, ln, 0);
+			else
+				mmsg(MANDOCERR_ROFFLOOP, curp, 
+				    curp->line, pos, NULL);
+			pos = 0;
 			continue;
-		} else if (ROFF_ERR == re) {
-			assert(MANDOCLEVEL_FATAL <= exit_status);
-			goto cleanup;
+		case (ROFF_APPEND):
+			pos = strlen(ln.buf);
+			continue;
+		case (ROFF_RERUN):
+			goto rerun;
+		case (ROFF_IGN):
+			pos = 0;
+			continue;
+		case (ROFF_ERR):
+			assert(MANDOCLEVEL_FATAL <= file_status);
+			break;
+		case (ROFF_SO):
+			if (pfile(ln.buf + of, curp)) {
+				pos = 0;
+				continue;
+			} else
+				break;
+		default:
+			break;
 		}
 
+		/*
+		 * If we encounter errors in the recursive parsebuf()
+		 * call, make sure we don't continue parsing.
+		 */
+
+		if (MANDOCLEVEL_FATAL <= file_status)
+			break;
+
 		/*
 		 * If input parsers have not been allocated, do so now.
 		 * We keep these instanced betwen parsers, but set them
@@ -536,132 +823,54 @@ fdesc(struct curparse *curp)
 		 * parsers with each one.
 		 */
 
-		if ( ! (man || mdoc))
-			pset(ln.buf + of, pos - of, curp, &man, &mdoc);
-
-		/* Lastly, push down into the parsers themselves. */
+		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.
+		 */
 
-		if (man && ! man_parseln(man, lnn_start, ln.buf, of)) {
-			assert(MANDOCLEVEL_FATAL <= exit_status);
-			goto cleanup;
-		}
-		if (mdoc && ! mdoc_parseln(mdoc, lnn_start, ln.buf, of)) {
-			assert(MANDOCLEVEL_FATAL <= exit_status);
-			goto cleanup;
+		if (ROFF_TBL == rr) {
+			assert(curp->man || curp->mdoc);
+			if (curp->man)
+				man_addspan(curp->man, roff_span(curp->roff));
+			else
+				mdoc_addspan(curp->mdoc, roff_span(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 ( ! rc) {
+				assert(MANDOCLEVEL_FATAL <= file_status);
+				break;
+			}
 		}
-	}
 
-	/* NOTE a parser may not have been assigned, yet. */
+		/* Temporary buffers typically are not full. */
 
-	if ( ! (man || mdoc)) {
-		fprintf(stderr, "%s: Not a manual\n", curp->file);
-		exit_status = MANDOCLEVEL_FATAL;
-		goto cleanup;
-	}
-
-	/* Clean up the parse routine ASTs. */
-
-	if (mdoc && ! mdoc_endparse(mdoc)) {
-		assert(MANDOCLEVEL_FATAL <= exit_status);
-		goto cleanup;
-	}
-	if (man && ! man_endparse(man)) {
-		assert(MANDOCLEVEL_FATAL <= exit_status);
-		goto cleanup;
-	}
-	if (roff && ! roff_endparse(roff)) {
-		assert(MANDOCLEVEL_FATAL <= exit_status);
-		goto cleanup;
-	}
-
-	/*
-	 * With -Wstop and warnings or errors of at least
-	 * the requested level, do not produce output.
-	 */
-
-	if (MANDOCLEVEL_OK != exit_status && 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);
-			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;
-			break;
-		case (OUTT_TREE):
-			curp->outman = tree_man;
-			curp->outmdoc = tree_mdoc;
-			break;
-		case (OUTT_PDF):
-			/* FALLTHROUGH */
-		case (OUTT_ASCII):
-			/* FALLTHROUGH */
-		case (OUTT_PS):
-			curp->outman = terminal_man;
-			curp->outmdoc = terminal_mdoc;
-			break;
-		default:
+		if (0 == start && '\0' == blk.buf[i])
 			break;
-		}
-	}
-
-	/* 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);
+		/* Start the next input line. */
 
- 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);
+		pos = 0;
+	}
 
-	return;
+	free(ln.buf);
 }
 
-
 static void
-pset(const char *buf, int pos, struct curparse *curp,
-		struct man **man, struct mdoc **mdoc)
+pset(const char *buf, int pos, struct curparse *curp)
 {
 	int		 i;
 
@@ -671,6 +880,9 @@ pset(const char *buf, int pos, struct curparse *curp,
 	 * 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]) {
@@ -683,36 +895,38 @@ pset(const char *buf, int pos, struct curparse *curp,
 
 	switch (curp->inttype) {
 	case (INTT_MDOC):
-		if (NULL == curp->mdoc) 
-			curp->mdoc = mdoc_alloc(&curp->regs, curp, mmsg);
-		assert(curp->mdoc);
-		*mdoc = curp->mdoc;
+		if (NULL == curp->pmdoc) 
+			curp->pmdoc = mdoc_alloc
+				(&curp->regs, curp, mmsg);
+		assert(curp->pmdoc);
+		curp->mdoc = curp->pmdoc;
 		return;
 	case (INTT_MAN):
-		if (NULL == curp->man) 
-			curp->man = man_alloc(&curp->regs, curp, mmsg);
-		assert(curp->man);
-		*man = curp->man;
+		if (NULL == curp->pman) 
+			curp->pman = man_alloc
+				(&curp->regs, curp, mmsg);
+		assert(curp->pman);
+		curp->man = curp->pman;
 		return;
 	default:
 		break;
 	}
 
 	if (pos >= 3 && 0 == memcmp(buf, ".Dd", 3))  {
-		if (NULL == curp->mdoc) 
-			curp->mdoc = mdoc_alloc(&curp->regs, curp, mmsg);
-		assert(curp->mdoc);
-		*mdoc = curp->mdoc;
+		if (NULL == curp->pmdoc) 
+			curp->pmdoc = mdoc_alloc
+				(&curp->regs, curp, mmsg);
+		assert(curp->pmdoc);
+		curp->mdoc = curp->pmdoc;
 		return;
 	} 
 
-	if (NULL == curp->man) 
-		curp->man = man_alloc(&curp->regs, curp, mmsg);
-	assert(curp->man);
-	*man = curp->man;
+	if (NULL == curp->pman) 
+		curp->pman = man_alloc(&curp->regs, curp, mmsg);
+	assert(curp->pman);
+	curp->man = curp->pman;
 }
 
-
 static int
 moptions(enum intt *tflags, char *arg)
 {
@@ -731,7 +945,6 @@ moptions(enum intt *tflags, char *arg)
 	return(1);
 }
 
-
 static int
 toptions(struct curparse *curp, char *arg)
 {
@@ -760,7 +973,6 @@ toptions(struct curparse *curp, char *arg)
 	return(1);
 }
 
-
 static int
 woptions(struct curparse *curp, char *arg)
 {
@@ -800,7 +1012,6 @@ woptions(struct curparse *curp, char *arg)
 	return(1);
 }
 
-
 static int
 mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg)
 {
@@ -809,6 +1020,7 @@ mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg)
 
 	level = MANDOCLEVEL_FATAL;
 	while (t < mandoclimits[level])
+		/* LINTED */
 		level--;
 
 	cp = (struct curparse *)arg;
@@ -821,8 +1033,8 @@ mmsg(enum mandocerr t, void *arg, int ln, int col, const char *msg)
 		fprintf(stderr, ": %s", msg);
 	fputc('\n', stderr);
 
-	if (exit_status < level)
-		exit_status = level;
+	if (file_status < level)
+		file_status = level;
 	
 	return(level < MANDOCLEVEL_FATAL);
 }