X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/5fa47110bac19f7e6acb9a32e76e800657e98799..c6cc6d2cdc21391e0fed1b0e6fd715071bee1879:/main.c

diff --git a/main.c b/main.c
index 902d7324..4c7db9c8 100644
--- a/main.c
+++ b/main.c
@@ -1,150 +1,256 @@
-/* $Id: main.c,v 1.15 2009/03/25 21:03:13 kristaps Exp $ */
+/*	$Id: main.c,v 1.114 2010/12/01 17:00:05 kristaps Exp $ */
 /*
- * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org>
+ * 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 copyright notice and this permission notice appear in all
- * copies.
+ * 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 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/mman.h>
 #include <sys/stat.h>
 
 #include <assert.h>
-#include <err.h>
+#include <ctype.h>
 #include <fcntl.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"
+#include "roff.h"
 
-#ifdef __linux__
-extern	int		  getsubopt(char **, char * const *, char **);
-# ifndef __dead
-#  define __dead __attribute__((__noreturn__))
-# endif
-#elif defined(__dead2)
-# ifndef __dead
-#  define __dead __dead2
-# endif
+#ifndef MAP_FILE
+#define	MAP_FILE	0
 #endif
 
+#define	UNCONST(a)	((void *)(uintptr_t)(const void *)(a))
+
+/* FIXME: Intel's compiler?  LLVM?  pcc?  */
+
+#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 *);
+
 struct	buf {
 	char	 	 *buf;
 	size_t		  sz;
 };
 
-struct	curparse {
-	const char	 *file;
-	int		  wflags;
-#define	WARN_WALL	  0x03		/* All-warnings mask. */
-#define	WARN_WCOMPAT	 (1 << 0)	/* Compatibility warnings. */
-#define	WARN_WSYNTAX	 (1 << 1)	/* Syntax warnings. */
-#define	WARN_WERR	 (1 << 2)	/* Warnings->errors. */
-};
-
-#define	IGN_SCOPE	 (1 << 0) /* Flag to ignore scope. */
-#define	IGN_ESCAPE	 (1 << 1) /* Flag to ignore bad escapes. */
-#define	IGN_MACRO	 (1 << 2) /* Flag to ignore unknown macros. */
-
 enum	intt {
-	INTT_MDOC = 0,
+	INTT_AUTO,
+	INTT_MDOC,
 	INTT_MAN
 };
 
 enum	outt {
 	OUTT_ASCII = 0,
-	OUTT_LATIN1,
-	OUTT_UTF8,
 	OUTT_TREE,
-	OUTT_LINT
+	OUTT_HTML,
+	OUTT_XHTML,
+	OUTT_LINT,
+	OUTT_PS,
+	OUTT_PDF
 };
 
-typedef	int		(*out_run)(void *, const struct man *,
-				const struct mdoc *);
-typedef	void		(*out_free)(void *);
+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 */
+	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 */
+};
 
-extern	char		 *__progname;
+static	const char * const	mandoclevels[MANDOCLEVEL_MAX] = {
+	"SUCCESS",
+	"RESERVED",
+	"WARNING",
+	"ERROR",
+	"FATAL",
+	"BADARG",
+	"SYSERR"
+};
 
-extern	void		 *ascii_alloc(void);
-extern	void		 *latin1_alloc(void);
-extern	void		 *utf8_alloc(void);
-extern	int		  terminal_run(void *, const struct man *, 
-				const struct mdoc *);
-extern	int		  tree_run(void *, const struct man *,
-				const struct mdoc *);
-extern	void		  terminal_free(void *);
+static	const enum mandocerr	mandoclimits[MANDOCLEVEL_MAX] = {
+	MANDOCERR_OK,
+	MANDOCERR_WARNING,
+	MANDOCERR_WARNING,
+	MANDOCERR_ERROR,
+	MANDOCERR_FATAL,
+	MANDOCERR_MAX,
+	MANDOCERR_MAX
+};
 
-static	int		  foptions(int *, char *);
-static	int		  toptions(enum outt *, char *);
+static	const char * const	mandocerrs[MANDOCERR_MAX] = {
+	"ok",
+
+	"generic warning",
+
+	".so is fragile, better use ln(1)",
+	"text should be uppercase",
+	"sections out of conventional order",
+	"section name repeats",
+	"out of order prologue",
+	"repeated prologue entry",
+	"list type must come first",
+	"tab in non-literal context",
+	"bad escape sequence",
+	"unterminated quoted string",
+	"argument requires the width argument",
+	"superfluous width argument",
+	"bad date argument",
+	"bad width argument",
+	"unknown manual section",
+	"section not in conventional manual section",
+	"end of line whitespace",
+	"blocks badly nested",
+
+	"generic error",
+
+	"NAME section must come first",
+	"bad Boolean value",
+	"child violates parent syntax",
+	"bad AT&T symbol",
+	"bad standard",
+	"list type repeated",
+	"display type repeated",
+	"argument repeated",
+	"ignoring argument",
+	"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",
+	"NOT IMPLEMENTED: skipping request",
+	"line scope broken",
+	"argument count wrong",
+	"request scope close w/none open",
+	"scope already open",
+	"scope open on exit",
+	"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",
+	"paragraph macro ignored",
+
+	"generic fatal error",
+
+	"column syntax is inconsistent",
+	"displays may not be nested",
+	"unsupported display type",
+	"blocks badly nested",
+	"no such block is open",
+	"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		  woptions(int *, char *);
-static	int		  merr(void *, int, int, const char *);
-static	int		  manwarn(void *, int, int, const char *);
-static	int		  mdocwarn(void *, int, int, 
-				enum mdoc_warn, const char *);
-static	int		  file(struct buf *, struct buf *, 
-				const char *, 
-				struct man *, struct mdoc *);
-static	int		  fdesc(struct buf *, struct buf *,
-				const char *, int, 
-				struct man *, struct mdoc *);
-
-__dead	static void	  version(void);
-__dead	static void	  usage(void);
+static	int		  mmsg(enum mandocerr, void *, 
+				int, int, const char *);
+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  exit_status = MANDOCLEVEL_OK;
 
 int
 main(int argc, char *argv[])
 {
-	int		 c, rc, fflags, pflags;
-	struct mdoc_cb	 mdoccb;
-	struct man_cb	 mancb;
-	struct man	*man;
-	struct mdoc	*mdoc;
-	void		*outdata;
-	enum outt	 outtype;
-	enum intt	 inttype;
-	struct buf	 ln, blk;
-	out_run		 outrun;
-	out_free	 outfree;
+	int		 c;
 	struct curparse	 curp;
 
-	fflags = 0;
-	outtype = OUTT_ASCII;
-	inttype = INTT_MDOC;
+	progname = strrchr(argv[0], '/');
+	if (progname == NULL)
+		progname = argv[0];
+	else
+		++progname;
+
+	memset(&curp, 0, sizeof(struct curparse));
 
-	bzero(&curp, sizeof(struct curparse));
+	curp.inttype = INTT_AUTO;
+	curp.outtype = OUTT_ASCII;
+	curp.wlevel  = MANDOCLEVEL_FATAL;
 
 	/* LINTED */
-	while (-1 != (c = getopt(argc, argv, "f:m:VW:T:")))
+	while (-1 != (c = getopt(argc, argv, "m:O:T:VW:")))
 		switch (c) {
-		case ('f'):
-			if ( ! foptions(&fflags, optarg))
-				return(0);
-			break;
 		case ('m'):
-			if ( ! moptions(&inttype, optarg))
-				return(0);
+			if ( ! moptions(&curp.inttype, optarg))
+				return((int)MANDOCLEVEL_BADARG);
+			break;
+		case ('O'):
+			(void)strlcat(curp.outopts, optarg, BUFSIZ);
+			(void)strlcat(curp.outopts, ",", BUFSIZ);
 			break;
 		case ('T'):
-			if ( ! toptions(&outtype, optarg))
-				return(0);
+			if ( ! toptions(&curp, optarg))
+				return((int)MANDOCLEVEL_BADARG);
 			break;
 		case ('W'):
-			if ( ! woptions(&curp.wflags, optarg))
-				return(0);
+			if ( ! woptions(&curp, optarg))
+				return((int)MANDOCLEVEL_BADARG);
 			break;
 		case ('V'):
 			version();
@@ -157,436 +263,725 @@ main(int argc, char *argv[])
 	argc -= optind;
 	argv += optind;
 
-	/*
-	 * Allocate the appropriate front-end.  Note that utf8, ascii
-	 * and latin1 all resolve to the terminal front-end with
-	 * different encodings (see terminal.c).  Not all frontends have
-	 * cleanup or alloc routines.
-	 */
+	if (NULL == *argv) {
+		curp.file = "<stdin>";
+		curp.fd = STDIN_FILENO;
 
-	switch (outtype) {
-	case (OUTT_LATIN1):
-		outdata = latin1_alloc();
-		outrun = terminal_run;
-		outfree = terminal_free;
-		break;
-	case (OUTT_UTF8):
-		outdata = utf8_alloc();
-		outrun = terminal_run;
-		outfree = terminal_free;
-		break;
-	case (OUTT_TREE):
-		outdata = NULL;
-		outrun = tree_run;
-		outfree = NULL;
-		break;
-	case (OUTT_LINT):
-		outdata = NULL;
-		outrun = NULL;
-		outfree = NULL;
-		break;
-	default:
-		outdata = ascii_alloc();
-		outrun = terminal_run;
-		outfree = terminal_free;
-		break;
+		fdesc(&curp);
 	}
 
-	/*
-	 * All callbacks route into here, where we print them onto the
-	 * screen.  XXX - for now, no path for debugging messages.
-	 */
+	while (*argv) {
+		ffile(*argv, &curp);
+		if (MANDOCLEVEL_OK != exit_status && curp.wstop)
+			break;
+		++argv;
+	}
 
-	mdoccb.mdoc_msg = NULL;
-	mdoccb.mdoc_err = merr;
-	mdoccb.mdoc_warn = mdocwarn;
+	if (curp.outfree)
+		(*curp.outfree)(curp.outdata);
+	if (curp.pmdoc)
+		mdoc_free(curp.pmdoc);
+	if (curp.pman)
+		man_free(curp.pman);
+	if (curp.roff)
+		roff_free(curp.roff);
 
-	mancb.man_err = merr;
-	mancb.man_warn = manwarn;
+	return((int)exit_status);
+}
 
-	bzero(&ln, sizeof(struct buf));
-	bzero(&blk, sizeof(struct buf));
 
-	man = NULL;
-	mdoc = NULL;
-	pflags = 0;
+static void
+version(void)
+{
 
-	switch (inttype) {
-	case (INTT_MAN):
-		if (fflags & IGN_MACRO)
-			pflags |= MAN_IGN_MACRO;
+	(void)printf("%s %s\n", progname, VERSION);
+	exit((int)MANDOCLEVEL_OK);
+}
 
-		man = man_alloc(&curp, pflags, &mancb);
-		break;
-	default:
-		if (fflags & IGN_SCOPE)
-			pflags |= MDOC_IGN_SCOPE;
-		if (fflags & IGN_ESCAPE)
-			pflags |= MDOC_IGN_ESCAPE;
-		if (fflags & IGN_MACRO)
-			pflags |= MDOC_IGN_MACRO;
-
-		mdoc = mdoc_alloc(&curp, pflags, &mdoccb);
-		break;
-	}
+
+static void
+usage(void)
+{
+
+	(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)
+{
 
 	/*
-	 * Loop around available files.
+	 * 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().
 	 */
 
-	if (NULL == *argv) {
-		curp.file = "<stdin>";
-		rc = 0;
-		c = fdesc(&blk, &ln, "stdin", 
-				STDIN_FILENO, man, mdoc);
-
-		if (c && NULL == outrun)
-			rc = 1;
-		else if (c && outrun && (*outrun)(outdata, man, mdoc))
-			rc = 1;
-	} else {
-		while (*argv) {
-			curp.file = *argv;
-			c = file(&blk, &ln, *argv, man, mdoc);
-			if ( ! c)
-				break;
-			if (outrun && ! (*outrun)(outdata, man, mdoc))
-				break;
-			if (man)
-				man_reset(man);
-			if (mdoc)
-				mdoc_reset(mdoc);
+	curp->file = file;
 
-			argv++;
-		}
-		rc = NULL == *argv;
+	if (-1 == (curp->fd = open(curp->file, O_RDONLY, 0))) {
+		perror(curp->file);
+		exit_status = MANDOCLEVEL_SYSERR;
+		return;
 	}
 
-	if (blk.buf)
-		free(blk.buf);
-	if (ln.buf)
-		free(ln.buf);
-	if (outfree)
-		(*outfree)(outdata);
-	if (mdoc)
-		mdoc_free(mdoc);
-	if (man)
-		man_free(man);
-
-	return(rc ? EXIT_SUCCESS : EXIT_FAILURE);
-}
+	fdesc(curp);
 
+	if (-1 == close(curp->fd))
+		perror(curp->file);
+}
 
-__dead static void
-version(void)
+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);
+		exit_status = MANDOCLEVEL_SYSERR;
+		return(0);
+	}
 
-	(void)printf("%s %s\n", __progname, VERSION);
-	exit(0);
-	/* NOTREACHED */
+	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 > exit_status ? 1 : 0);
 }
 
 
-__dead static void
-usage(void)
+static void
+resize_buf(struct buf *buf, size_t initial)
 {
 
-	(void)fprintf(stderr, "usage: %s [-V] [-foption...] "
-			"[-mformat] [-Toutput] [-Werr...]\n", 
-			__progname);
-	exit(1);
-	/* NOTREACHED */
+	buf->sz = buf->sz ? 2 * buf->sz : initial;
+	buf->buf = realloc(buf->buf, buf->sz);
+	if (NULL == buf->buf) {
+		perror(NULL);
+		exit((int)MANDOCLEVEL_SYSERR);
+	}
 }
 
 
 static int
-file(struct buf *blk, struct buf *ln, const char *file, 
-		struct man *man, struct mdoc *mdoc)
+read_whole_file(struct curparse *curp, struct buf *fb, int *with_mmap)
 {
-	int		 fd, c;
+	struct stat	 st;
+	size_t		 off;
+	ssize_t		 ssz;
 
-	if (-1 == (fd = open(file, O_RDONLY, 0))) {
-		warn("%s", file);
+	if (-1 == fstat(curp->fd, &st)) {
+		perror(curp->file);
 		return(0);
 	}
 
-	c = fdesc(blk, ln, file, fd, man, mdoc);
+	/*
+	 * 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 == close(fd))
-		warn("%s", file);
+	if (S_ISREG(st.st_mode)) {
+		if (st.st_size >= (1U << 31)) {
+			fprintf(stderr, "%s: input too large\n", 
+					curp->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, curp->fd, 0);
+		if (fb->buf != MAP_FAILED)
+			return(1);
+	}
 
-	return(c);
+	/*
+	 * 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", 
+						curp->file);
+				break;
+			}
+			resize_buf(fb, 65536);
+		}
+		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);
+			break;
+		}
+		off += (size_t)ssz;
+	}
+
+	free(fb->buf);
+	fb->buf = NULL;
+	return(0);
 }
 
 
-static int
-fdesc(struct buf *blk, struct buf *ln,
-		const char *f, int fd, 
-		struct man *man, struct mdoc *mdoc)
+static void
+fdesc(struct curparse *curp)
 {
-	size_t		 sz;
-	ssize_t		 ssz;
-	struct stat	 st;
-	int		 j, i, pos, lnn;
 
-	assert( ! (man && mdoc));
+	/*
+	 * 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. */
+
+	curp->mdoc = NULL;
+	curp->man = NULL;
+
+	/* 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 <= exit_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);
+		exit_status = MANDOCLEVEL_FATAL;
+		goto cleanup;
+	}
+
+	/* Clean up the parse routine ASTs. */
+
+	if (curp->mdoc && ! mdoc_endparse(curp->mdoc)) {
+		assert(MANDOCLEVEL_FATAL <= exit_status);
+		goto cleanup;
+	}
+
+	if (curp->man && ! man_endparse(curp->man)) {
+		assert(MANDOCLEVEL_FATAL <= exit_status);
+		goto cleanup;
+	}
+
+	assert(curp->roff);
+	if ( ! roff_endparse(curp->roff)) {
+		assert(MANDOCLEVEL_FATAL <= exit_status);
+		goto cleanup;
+	}
 
 	/*
-	 * Two buffers: ln and buf.  buf is the input buffer, optimised
-	 * for each file's block size.  ln is a line buffer.  Both
-	 * growable, hence passed in by ptr-ptr.
+	 * With -Wstop and warnings or errors of at least
+	 * the requested level, do not produce output.
 	 */
 
-	sz = BUFSIZ;
+	if (MANDOCLEVEL_OK != exit_status && curp->wstop)
+		goto cleanup;
+
+	/* If unset, allocate output dev now (if applicable). */
 
-	if (-1 == fstat(fd, &st))
-		warnx("%s", f);
-	else if ((size_t)st.st_blksize > sz)
-		sz = st.st_blksize;
+	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;
+		}
 
-	if (sz > blk->sz) {
-		blk->buf = realloc(blk->buf, sz);
-		if (NULL == blk->buf)
-			err(1, "realloc");
-		blk->sz = sz;
+		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);
+
+	return;
+}
+
+static void
+pdesc(struct curparse *curp)
+{
+	struct buf	 blk;
+	int		 with_mmap;
+
 	/*
-	 * Fill buf with file blocksize and parse newlines into ln.
+	 * 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.
 	 */
 
-	for (lnn = 1, pos = 0; ; ) {
-		if (-1 == (ssz = read(fd, blk->buf, sz))) {
-			warn("%s", f);
-			return(0);
-		} else if (0 == ssz) 
+	if ( ! read_whole_file(curp, &blk, &with_mmap)) {
+		exit_status = MANDOCLEVEL_SYSERR;
+		return;
+	}
+
+	/* Line number is per-file. */
+
+	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;
 
-		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)
-					err(1, "realloc");
+		if (start)
+			curp->line = lnn;
+
+		while (i < (int)blk.sz && (start || '\0' != blk.buf[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 drop these characters 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)))) {
+				mmsg(MANDOCERR_BADCHAR, curp, 
+				    curp->line, pos, "ignoring byte");
+				i++;
+				continue;
 			}
 
-			if ('\n' != blk->buf[i]) {
-				ln->buf[pos++] = blk->buf[i];
+			/* 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;
 			}
 
-			/* Check for CPP-escaped newline.  */
+			/* Found escape & at least one other char. */
 
-			if (pos > 0 && '\\' == ln->buf[pos - 1]) {
-				for (j = pos - 1; j >= 0; j--)
-					if ('\\' != ln->buf[j])
+			if ('\n' == blk.buf[i + 1]) {
+				i += 2;
+				/* Escaped newlines are skipped over */
+				++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;
+					}
+				}
 
-				if ( ! ((pos - j) % 2)) {
-					pos--;
-					lnn++;
-					continue;
+				/* Backout trailing whitespaces */
+				for (; pos > 0; --pos) {
+					if (ln.buf[pos - 1] != ' ')
+						break;
+					if (pos > 2 && ln.buf[pos - 2] == '\\')
+						break;
 				}
+				break;
 			}
 
-			ln->buf[pos] = 0;
-			if (mdoc && ! mdoc_parseln(mdoc, lnn, ln->buf))
-				return(0);
-			if (man && ! man_parseln(man, lnn, ln->buf))
-				return(0);
-			lnn++;
+			/* 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;
+
+rerun:
+		rr = roff_parseln
+			(curp->roff, curp->line, 
+			 &ln.buf, &ln.sz, of, &of);
+
+		switch (rr) {
+		case (ROFF_REPARSE):
+			parsebuf(curp, ln, 0);
 			pos = 0;
+			continue;
+		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 <= exit_status);
+			break;
+		case (ROFF_SO):
+			if (pfile(ln.buf + of, curp)) {
+				pos = 0;
+				continue;
+			} else
+				break;
+		case (ROFF_CONT):
+			break;
+		}
+
+		/*
+		 * 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 ( ! (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 (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 <= exit_status);
+				break;
+			}
 		}
-	}
 
-	if (mdoc)
-	       return(mdoc_endparse(mdoc));
+		/* Temporary buffers typically are not full. */
 
-	return(man_endparse(man));
+		if (0 == start && '\0' == blk.buf[i])
+			break;
+
+		/* Start the next input line. */
+
+		pos = 0;
+	}
+
+	free(ln.buf);
 }
 
+static void
+pset(const char *buf, int pos, struct curparse *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 (INTT_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->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->pmdoc) 
+			curp->pmdoc = mdoc_alloc
+				(&curp->regs, curp, mmsg);
+		assert(curp->pmdoc);
+		curp->mdoc = curp->pmdoc;
+		return;
+	} 
+
+	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)
 {
 
-	if (0 == strcmp(arg, "mdoc"))
+	if (0 == strcmp(arg, "doc"))
 		*tflags = INTT_MDOC;
-	else if (0 == strcmp(arg, "man"))
+	else if (0 == strcmp(arg, "andoc"))
+		*tflags = INTT_AUTO;
+	else if (0 == strcmp(arg, "an"))
 		*tflags = INTT_MAN;
 	else {
-		warnx("bad argument: -m%s", arg);
+		fprintf(stderr, "%s: Bad argument\n", arg);
 		return(0);
 	}
 
 	return(1);
 }
 
-
 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, "latin1"))
-		*tflags = OUTT_LATIN1;
-	else if (0 == strcmp(arg, "utf8"))
-		*tflags = OUTT_UTF8;
-	else if (0 == strcmp(arg, "lint"))
-		*tflags = OUTT_LINT;
+		curp->outtype = OUTT_ASCII;
+	else if (0 == strcmp(arg, "lint")) {
+		curp->outtype = OUTT_LINT;
+		curp->wlevel  = MANDOCLEVEL_WARNING;
+	}
 	else if (0 == strcmp(arg, "tree"))
-		*tflags = OUTT_TREE;
+		curp->outtype = OUTT_TREE;
+	else if (0 == strcmp(arg, "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 {
-		warnx("bad argument: -T%s", arg);
+		fprintf(stderr, "%s: Bad argument\n", arg);
 		return(0);
 	}
 
 	return(1);
 }
 
-
-/*
- * Parse out the options for [-fopt...] setting compiler options.  These
- * can be comma-delimited or called again.
- */
 static int
-foptions(int *fflags, char *arg)
+woptions(struct curparse *curp, char *arg)
 {
-	char		*v;
-	char		*toks[4];
-
-	toks[0] = "ign-scope";
-	toks[1] = "ign-escape";
-	toks[2] = "ign-macro";
-	toks[3] = NULL;
+	char		*v, *o;
+	const char	*toks[6]; 
 
-	while (*arg) 
-		switch (getsubopt(&arg, toks, &v)) {
-		case (0):
-			*fflags |= IGN_SCOPE;
-			break;
-		case (1):
-			*fflags |= IGN_ESCAPE;
-			break;
-		case (2):
-			*fflags |= IGN_MACRO;
-			break;
-		default:
-			warnx("bad argument: -f%s", arg);
-			return(0);
-		}
-
-	return(1);
-}
-
-
-/* 
- * Parse out the options for [-Werr...], which sets warning modes.
- * These can be comma-delimited or called again.  
- */
-static int
-woptions(int *wflags, char *arg)
-{
-	char		*v;
-	char		*toks[5]; 
-
-	toks[0] = "all";
-	toks[1] = "compat";
-	toks[2] = "syntax";
+	toks[0] = "stop";
+	toks[1] = "all";
+	toks[2] = "warning";
 	toks[3] = "error";
-	toks[4] = NULL;
+	toks[4] = "fatal";
+	toks[5] = NULL;
 
-	while (*arg) 
-		switch (getsubopt(&arg, toks, &v)) {
+	while (*arg) {
+		o = arg;
+		switch (getsubopt(&arg, UNCONST(toks), &v)) {
 		case (0):
-			*wflags |= WARN_WALL;
+			curp->wstop = 1;
 			break;
 		case (1):
-			*wflags |= WARN_WCOMPAT;
-			break;
+			/* FALLTHROUGH */
 		case (2):
-			*wflags |= WARN_WSYNTAX;
+			curp->wlevel = MANDOCLEVEL_WARNING;
 			break;
 		case (3):
-			*wflags |= WARN_WERR;
+			curp->wlevel = MANDOCLEVEL_ERROR;
+			break;
+		case (4):
+			curp->wlevel = MANDOCLEVEL_FATAL;
 			break;
 		default:
-			warnx("bad argument: -W%s", arg);
+			fprintf(stderr, "-W%s: Bad argument\n", o);
 			return(0);
 		}
-
-	return(1);
-}
-
-
-/* ARGSUSED */
-static int
-merr(void *arg, int line, int col, const char *msg)
-{
-	struct curparse *curp;
-
-	curp = (struct curparse *)arg;
-
-	warnx("%s:%d: error: %s (column %d)", 
-			curp->file, line, msg, col);
-	return(0);
-}
-
-
-static int
-mdocwarn(void *arg, int line, int col, 
-		enum mdoc_warn type, const char *msg)
-{
-	struct curparse *curp;
-	char		*wtype;
-
-	curp = (struct curparse *)arg;
-	wtype = NULL;
-
-	switch (type) {
-	case (WARN_COMPAT):
-		wtype = "compat";
-		if (curp->wflags & WARN_WCOMPAT)
-			break;
-		return(1);
-	case (WARN_SYNTAX):
-		wtype = "syntax";
-		if (curp->wflags & WARN_WSYNTAX)
-			break;
-		return(1);
 	}
 
-	assert(wtype);
-	warnx("%s:%d: %s warning: %s (column %d)", 
-			curp->file, line, wtype, msg, col);
-
-	if ( ! (curp->wflags & WARN_WERR))
-		return(1);
-
-	warnx("%s: considering warnings as errors", 
-			__progname);
-	return(0);
+	return(1);
 }
 
-
 static int
-manwarn(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;
+	enum mandoclevel level;
 
-	curp = (struct curparse *)arg;
+	level = MANDOCLEVEL_FATAL;
+	while (t < mandoclimits[level])
+		/* LINTED */
+		level--;
 
-	if ( ! (curp->wflags & WARN_WSYNTAX))
+	cp = (struct curparse *)arg;
+	if (level < cp->wlevel)
 		return(1);
 
-	warnx("%s:%d: syntax warning: %s (column %d)", 
-			curp->file, line, msg, col);
-
-	if ( ! (curp->wflags & WARN_WERR))
-		return(1);
+	fprintf(stderr, "%s:%d:%d: %s: %s",
+	    cp->file, ln, col + 1, mandoclevels[level], mandocerrs[t]);
+	if (msg)
+		fprintf(stderr, ": %s", msg);
+	fputc('\n', stderr);
 
-	warnx("%s: considering warnings as errors", 
-			__progname);
-	return(0);
+	if (exit_status < level)
+		exit_status = level;
+	
+	return(level < MANDOCLEVEL_FATAL);
 }