X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/ea750887d0d1dfad81d0f7e066c720defe06e08a..71cd7398da2aa89d10c8268134292464d0dd22d2:/mdoc_macro.c

diff --git a/mdoc_macro.c b/mdoc_macro.c
index 63d5ef83..6754c9a0 100644
--- a/mdoc_macro.c
+++ b/mdoc_macro.c
@@ -1,4 +1,4 @@
-/*	$Id: mdoc_macro.c,v 1.35 2009/09/16 20:49:06 kristaps Exp $ */
+/*	$Id: mdoc_macro.c,v 1.68 2010/05/17 22:11:42 kristaps Exp $ */
 /*
  * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
  *
@@ -14,44 +14,55 @@
  * 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 "mandoc.h"
 #include "libmdoc.h"
+#include "libmandoc.h"
+
+enum	rew {
+	REWIND_REWIND,
+	REWIND_NOHALT,
+	REWIND_HALT
+};
 
-#define	REWIND_REWIND	(1 << 0)
-#define	REWIND_NOHALT	(1 << 1)
-#define	REWIND_HALT	(1 << 2)
-
-static	int	  obsolete(MACRO_PROT_ARGS);
-static	int	  blk_part_exp(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	  blk_full(MACRO_PROT_ARGS);
-static	int	  blk_exp_close(MACRO_PROT_ARGS);
-static	int	  blk_part_imp(MACRO_PROT_ARGS);
-
-static	int	  phrase(struct mdoc *, int, int, char *);
-static	int	  rew_dohalt(int, enum mdoc_type, 
-			const struct mdoc_node *);
-static	int	  rew_alt(int);
-static	int	  rew_dobreak(int, const struct mdoc_node *);
-static	int	  rew_elem(struct mdoc *, int);
-static	int	  rew_sub(enum mdoc_type, struct mdoc *, 
-			int, int, int);
-static	int	  rew_last(struct mdoc *, 
-			const struct mdoc_node *);
-static	int	  append_delims(struct mdoc *, int, int *, char *);
-static	int	  lookup(int, const char *);
-static	int	  lookup_raw(const char *);
-static	int	  swarn(struct mdoc *, enum mdoc_type, int, int, 
-			const struct mdoc_node *);
-
-/* Central table of library: who gets parsed how. */
+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	  	append_delims(struct mdoc *, 
+				int, int *, char *);
+static	enum mdoct	lookup(enum mdoct, const char *);
+static	enum mdoct	lookup_raw(const char *);
+static	int	  	phrase(struct mdoc *, int, int, 
+				char *, enum margserr);
+static	enum mdoct 	rew_alt(enum mdoct);
+static	int	  	rew_dobreak(enum mdoct, 
+				const struct mdoc_node *);
+static	enum rew  	rew_dohalt(enum mdoct, enum mdoc_type, 
+				const struct mdoc_node *);
+static	int	  	rew_elem(struct mdoc *, enum mdoct);
+static	int	  	rew_last(struct mdoc *, 
+				const struct mdoc_node *);
+static	int	  	rew_sub(enum mdoc_type, struct mdoc *, 
+				enum mdoct, int, int);
+static	int	  	swarn(struct mdoc *, enum mdoc_type, int, 
+				int, const struct mdoc_node *);
 
 const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* Ap */
@@ -93,8 +104,8 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ in_line_eoln, 0 }, /* Rv */
 	{ in_line_argn, MDOC_CALLABLE | MDOC_PARSED }, /* St */ 
 	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Va */
-	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */ 
-	{ in_line, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */
+	{ 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 */
@@ -131,7 +142,7 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ 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_PARSED | MDOC_IGNDELIM }, /* Pf */
+	{ 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 */
@@ -174,6 +185,7 @@ const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
 	{ in_line_eoln, 0 }, /* %Q */
 	{ in_line_eoln, 0 }, /* br */
 	{ in_line_eoln, 0 }, /* sp */
+	{ in_line_eoln, 0 }, /* %U */
 };
 
 const	struct mdoc_macro * const mdoc_macros = __mdoc_macros;
@@ -184,6 +196,7 @@ swarn(struct mdoc *mdoc, enum mdoc_type type,
 		int line, int pos, const struct mdoc_node *p)
 {
 	const char	*n, *t, *tt;
+	int		 rc;
 
 	n = t = "<root>";
 	tt = "block";
@@ -216,13 +229,11 @@ swarn(struct mdoc *mdoc, enum mdoc_type type,
 		break;
 	}
 
-	if ( ! (MDOC_IGN_SCOPE & mdoc->pflags))
-		return(mdoc_verr(mdoc, line, pos, 
-				"%s scope breaks %s scope of %s", 
-				tt, t, n));
-	return(mdoc_vwarn(mdoc, line, pos, 
-				"%s scope breaks %s scope of %s", 
-				tt, t, n));
+	rc = mdoc_vmsg(mdoc, MANDOCERR_SCOPE, line, pos,
+			"%s scope breaks %s of %s", tt, t, n);
+
+	/* FIXME: logic should be in driver. */
+	return(MDOC_IGN_SCOPE & mdoc->pflags ? rc : 0);
 }
 
 
@@ -245,7 +256,8 @@ mdoc_macroend(struct mdoc *m)
 			continue;
 		if ( ! (MDOC_EXPLICIT & mdoc_macros[n->tok].flags))
 			continue;
-		return(mdoc_nerr(m, n, EOPEN));
+		mdoc_nmsg(m, n, MANDOCERR_SYNTSCOPE);
+		return(0);
 	}
 
 	/* Rewind to the first. */
@@ -257,9 +269,10 @@ mdoc_macroend(struct mdoc *m)
 /*
  * Look up a macro from within a subsequent context.
  */
-static int
-lookup(int from, const char *p)
+static enum mdoct
+lookup(enum mdoct from, const char *p)
 {
+	/* FIXME: make -diag lists be un-PARSED. */
 
 	if ( ! (MDOC_PARSED & mdoc_macros[from].flags))
 		return(MDOC_MAX);
@@ -270,10 +283,10 @@ lookup(int from, const char *p)
 /*
  * Lookup a macro following the initial line macro.
  */
-static int
+static enum mdoct
 lookup_raw(const char *p)
 {
-	int		 res;
+	enum mdoct	 res;
 
 	if (MDOC_MAX == (res = mdoc_hash_find(p)))
 		return(MDOC_MAX);
@@ -310,8 +323,8 @@ rew_last(struct mdoc *mdoc, const struct mdoc_node *to)
  * Return the opening macro of a closing one, e.g., `Ec' has `Eo' as its
  * matching pair.
  */
-static int
-rew_alt(int tok)
+static enum mdoct
+rew_alt(enum mdoct tok)
 {
 	switch (tok) {
 	case (MDOC_Ac):
@@ -360,8 +373,9 @@ rew_alt(int tok)
  * close our current scope (REWIND_REWIND), or continue (REWIND_NOHALT).
  * The scope-closing and so on occurs in the various rew_* routines.
  */
-static int 
-rew_dohalt(int tok, enum mdoc_type type, const struct mdoc_node *p)
+static enum rew
+rew_dohalt(enum mdoct tok, enum mdoc_type type, 
+		const struct mdoc_node *p)
 {
 
 	if (MDOC_ROOT == p->type)
@@ -391,6 +405,8 @@ rew_dohalt(int tok, enum mdoc_type type, const struct mdoc_node *p)
 	case (MDOC_Qq):
 		/* FALLTHROUGH */
 	case (MDOC_Sq):
+		/* FALLTHROUGH */
+	case (MDOC_Vt):
 		assert(MDOC_TAIL != type);
 		if (type == p->type && tok == p->tok)
 			return(REWIND_REWIND);
@@ -498,7 +514,7 @@ rew_dohalt(int tok, enum mdoc_type type, const struct mdoc_node *p)
  * REWIND_NOHALT). 
  */
 static int
-rew_dobreak(int tok, const struct mdoc_node *p)
+rew_dobreak(enum mdoct tok, const struct mdoc_node *p)
 {
 
 	assert(MDOC_ROOT != p->type);
@@ -527,7 +543,6 @@ rew_dobreak(int tok, const struct mdoc_node *p)
 			return(1);
 		break;
 	case (MDOC_Oc):
-		/* XXX - experimental! */
 		if (MDOC_Op == p->tok)
 			return(1);
 		break;
@@ -545,7 +560,7 @@ rew_dobreak(int tok, const struct mdoc_node *p)
 
 
 static int
-rew_elem(struct mdoc *mdoc, int tok)
+rew_elem(struct mdoc *mdoc, enum mdoct tok)
 {
 	struct mdoc_node *n;
 
@@ -561,10 +576,10 @@ rew_elem(struct mdoc *mdoc, int tok)
 
 static int
 rew_sub(enum mdoc_type t, struct mdoc *m, 
-		int tok, int line, int ppos)
+		enum mdoct tok, int line, int ppos)
 {
 	struct mdoc_node *n;
-	int		  c;
+	enum rew	  c;
 
 	/* LINTED */
 	for (n = m->last; n; n = n->parent) {
@@ -574,7 +589,9 @@ rew_sub(enum mdoc_type t, struct mdoc *m,
 				return(1);
 			if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags))
 				return(1);
-			return(mdoc_perr(m, line, ppos, ENOCTX));
+			/* FIXME: shouldn't raise an error */
+			mdoc_pmsg(m, line, ppos, MANDOCERR_SYNTNOSCOPE);
+			return(0);
 		}
 		if (REWIND_REWIND == c)
 			break;
@@ -585,31 +602,64 @@ rew_sub(enum mdoc_type t, struct mdoc *m,
 	}
 
 	assert(n);
-	return(rew_last(m, n));
+	if ( ! rew_last(m, n))
+		return(0);
+
+#ifdef	UGLY
+	/*
+	 * The current block extends an enclosing block beyond a line
+	 * break.  Now that the current block ends, close the enclosing
+	 * block, too.
+	 */
+	if (NULL != (n = n->pending)) {
+		assert(MDOC_HEAD == n->type);
+		if ( ! rew_last(m, n))
+			return(0);
+		if ( ! mdoc_body_alloc(m, n->line, n->pos, n->tok))
+			return(0);
+	}
+#endif
+
+	return(1);
 }
 
 
 static int
-append_delims(struct mdoc *mdoc, int line, int *pos, char *buf)
+append_delims(struct mdoc *m, int line, int *pos, char *buf)
 {
-	int		 c, lastarg;
+	int		 la;
+	enum margserr	 ac;
 	char		*p;
 
-	if (0 == buf[*pos])
+	if ('\0' == buf[*pos])
 		return(1);
 
 	for (;;) {
-		lastarg = *pos;
-		c = mdoc_zargs(mdoc, line, pos, buf, ARGS_NOWARN, &p);
-		assert(ARGS_PHRASE != c);
+		la = *pos;
+		ac = mdoc_zargs(m, line, pos, buf, ARGS_NOWARN, &p);
 
-		if (ARGS_ERROR == c)
+		if (ARGS_ERROR == ac)
 			return(0);
-		else if (ARGS_EOLN == c)
+		else if (ARGS_EOLN == ac)
 			break;
-		assert(mdoc_isdelim(p));
-		if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
+
+		assert(DELIM_NONE != mdoc_isdelim(p));
+		if ( ! mdoc_word_alloc(m, line, la, p))
 			return(0);
+
+		/*
+		 * If we encounter end-of-sentence symbols, then trigger
+		 * the double-space.
+		 *
+		 * XXX: it's easy to allow this to propogate 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 propogate the double-space.
+		 */
+		if (mandoc_eos(p, strlen(p)))
+			m->last->flags |= MDOC_EOS;
 	}
 
 	return(1);
@@ -622,9 +672,13 @@ append_delims(struct mdoc *mdoc, int line, int *pos, char *buf)
 static int
 blk_exp_close(MACRO_PROT_ARGS)
 {
-	int	 	 j, c, lastarg, maxargs, flushed;
+	int	 	 j, lastarg, maxargs, flushed, nl;
+	enum margserr	 ac;
+	enum mdoct	 ntok;
 	char		*p;
 
+	nl = MDOC_NEWLINE & m->flags;
+
 	switch (tok) {
 	case (MDOC_Ec):
 		maxargs = 1;
@@ -635,8 +689,9 @@ blk_exp_close(MACRO_PROT_ARGS)
 	}
 
 	if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) {
+		/* FIXME: do this in validate */
 		if (buf[*pos]) 
-			if ( ! mdoc_pwarn(m, line, ppos, ENOLINE))
+			if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_ARGSLOST))
 				return(0);
 
 		if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
@@ -660,34 +715,37 @@ blk_exp_close(MACRO_PROT_ARGS)
 			flushed = 1;
 		}
 
-		c = mdoc_args(m, line, pos, buf, tok, &p);
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
 
-		if (ARGS_ERROR == c)
+		if (ARGS_ERROR == ac)
 			return(0);
-		if (ARGS_PUNCT == c)
+		if (ARGS_PUNCT == ac)
 			break;
-		if (ARGS_EOLN == c)
+		if (ARGS_EOLN == ac)
 			break;
 
-		if (MDOC_MAX != (c = lookup(tok, p))) {
-			if ( ! flushed) {
-				if ( ! rew_sub(MDOC_BLOCK, m, tok, line, ppos))
-					return(0);
-				flushed = 1;
-			}
-			if ( ! mdoc_macro(m, c, line, lastarg, pos, buf))
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! mdoc_word_alloc(m, line, lastarg, p))
 				return(0);
-			break;
-		} 
+			continue;
+		}
 
-		if ( ! mdoc_word_alloc(m, line, lastarg, p))
+		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 (ppos > 1)
+	if ( ! nl)
 		return(1);
 	return(append_delims(m, line, pos, buf));
 }
@@ -696,14 +754,21 @@ blk_exp_close(MACRO_PROT_ARGS)
 static int
 in_line(MACRO_PROT_ARGS)
 {
-	int		  la, lastpunct, c, w, cnt, d, nc;
-	struct mdoc_arg	 *arg;
-	char		 *p;
+	int		 la, lastpunct, 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 */
@@ -725,15 +790,15 @@ in_line(MACRO_PROT_ARGS)
 
 	for (arg = NULL;; ) {
 		la = *pos;
-		c = mdoc_argv(m, line, tok, &arg, pos, buf);
+		av = mdoc_argv(m, line, tok, &arg, pos, buf);
 
-		if (ARGV_WORD == c) {
+		if (ARGV_WORD == av) {
 			*pos = la;
 			break;
 		} 
-		if (ARGV_EOLN == c)
+		if (ARGV_EOLN == av)
 			break;
-		if (ARGV_ARG == c)
+		if (ARGV_ARG == av)
 			continue;
 
 		mdoc_argv_free(arg);
@@ -742,18 +807,16 @@ in_line(MACRO_PROT_ARGS)
 
 	for (cnt = 0, lastpunct = 1;; ) {
 		la = *pos;
-		w = mdoc_args(m, line, pos, buf, tok, &p);
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
 
-		if (ARGS_ERROR == w)
+		if (ARGS_ERROR == ac)
 			return(0);
-		if (ARGS_EOLN == w)
+		if (ARGS_EOLN == ac)
 			break;
-		if (ARGS_PUNCT == w)
+		if (ARGS_PUNCT == ac)
 			break;
 
-		/* Quoted words shouldn't be looked-up. */
-
-		c = ARGS_QWORD == w ? MDOC_MAX : lookup(tok, p);
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
 		/* 
 		 * In this case, we've located a submacro and must
@@ -762,7 +825,7 @@ in_line(MACRO_PROT_ARGS)
 		 * or raise a warning.
 		 */
 
-		if (MDOC_MAX != c) {
+		if (MDOC_MAX != ntok) {
 			if (0 == lastpunct && ! rew_elem(m, tok))
 				return(0);
 			if (nc && 0 == cnt) {
@@ -772,13 +835,12 @@ in_line(MACRO_PROT_ARGS)
 					return(0);
 			} else if ( ! nc && 0 == cnt) {
 				mdoc_argv_free(arg);
-				if ( ! mdoc_pwarn(m, line, ppos, EIGNE))
+				if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY))
 					return(0);
 			}
-			c = mdoc_macro(m, c, line, la, pos, buf);
-			if (0 == c)
+			if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
 				return(0);
-			if (ppos > 1)
+			if ( ! nl)
 				return(1);
 			return(append_delims(m, line, pos, buf));
 		} 
@@ -789,9 +851,9 @@ in_line(MACRO_PROT_ARGS)
 		 * the word. 
 		 */
 
-		d = mdoc_isdelim(p);
+		d = ARGS_QWORD == ac ? DELIM_NONE : mdoc_isdelim(p);
 
-		if (ARGS_QWORD != w && d) {
+		if (ARGS_QWORD != ac && DELIM_NONE != d) {
 			if (0 == lastpunct && ! rew_elem(m, tok))
 				return(0);
 			lastpunct = 1;
@@ -801,10 +863,21 @@ in_line(MACRO_PROT_ARGS)
 			lastpunct = 0;
 		}
 
-		if ( ! d)
+		if (DELIM_NONE == d)
 			cnt++;
 		if ( ! mdoc_word_alloc(m, line, la, p))
 			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 (0 == lastpunct && MDOC_Fl == tok) {
+			if ( ! rew_elem(m, tok))
+				return(0);
+			lastpunct = 1;
+		}
 	}
 
 	if (0 == lastpunct && ! rew_elem(m, tok))
@@ -814,8 +887,8 @@ in_line(MACRO_PROT_ARGS)
 	 * 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);
@@ -823,11 +896,11 @@ in_line(MACRO_PROT_ARGS)
 			return(0);
 	} else if ( ! nc && 0 == cnt) {
 		mdoc_argv_free(arg);
-		if ( ! mdoc_pwarn(m, line, ppos, EIGNE))
+		if ( ! mdoc_pmsg(m, line, ppos, MANDOCERR_MACROEMPTY))
 			return(0);
 	}
 
-	if (ppos > 1)
+	if ( ! nl)
 		return(1);
 	return(append_delims(m, line, pos, buf));
 }
@@ -836,25 +909,21 @@ in_line(MACRO_PROT_ARGS)
 static int
 blk_full(MACRO_PROT_ARGS)
 {
-	int		  c, lastarg, reopen, dohead;
+	int		  la, nl;
 	struct mdoc_arg	 *arg;
+	struct mdoc_node *head; /* save of head macro */
+	struct mdoc_node *body; /* save of body macro */
+#ifdef	UGLY
+	struct mdoc_node *n;
+#endif
+	enum mdoct	  ntok;
+	enum margserr	  ac, lac;
+	enum margverr	  av;
 	char		 *p;
 
-	/* 
-	 * Whether to process a block-head section.  If this is
-	 * non-zero, then a head will be opened for all line arguments.
-	 * If not, then the head will always be empty and only a body
-	 * will be opened, which will stay open at the eoln.
-	 */
+	nl = MDOC_NEWLINE & m->flags;
 
-	switch (tok) {
-	case (MDOC_Nd):
-		dohead = 0;
-		break;
-	default:
-		dohead = 1;
-		break;
-	}
+	/* Close out prior implicit scope. */
 
 	if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) {
 		if ( ! rew_sub(MDOC_BODY, m, tok, line, ppos))
@@ -863,18 +932,27 @@ blk_full(MACRO_PROT_ARGS)
 			return(0);
 	}
 
+	/*
+	 * This routine accomodates 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;; ) {
-		lastarg = *pos;
-		c = mdoc_argv(m, line, tok, &arg, pos, buf);
+		la = *pos;
+		av = mdoc_argv(m, line, tok, &arg, pos, buf);
 
-		if (ARGV_WORD == c) {
-			*pos = lastarg;
+		if (ARGV_WORD == av) {
+			*pos = la;
 			break;
 		} 
 
-		if (ARGV_EOLN == c)
+		if (ARGV_EOLN == av)
 			break;
-		if (ARGV_ARG == c)
+		if (ARGV_ARG == av)
 			continue;
 
 		mdoc_argv_free(arg);
@@ -884,72 +962,123 @@ blk_full(MACRO_PROT_ARGS)
 	if ( ! mdoc_block_alloc(m, line, ppos, tok, arg))
 		return(0);
 
-	if (0 == buf[*pos]) {
+	head = body = NULL;
+
+	/*
+	 * 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);
-		return(1);
-	}
+		body = m->last;
+	} 
 
-	if ( ! mdoc_head_alloc(m, line, ppos, tok))
-		return(0);
+	ac = ARGS_ERROR;
 
-	/* Immediately close out head and enter body, if applicable. */
+	for ( ; ; ) {
+		la = *pos;
+		lac = ac;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
 
-	if (0 == dohead) {
-		if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+		if (ARGS_ERROR == ac)
 			return(0);
-		if ( ! mdoc_body_alloc(m, line, ppos, tok))
-			return(0);
-	} 
+		if (ARGS_EOLN == ac)
+			break;
 
-	for (reopen = 0;; ) {
-		lastarg = *pos;
-		c = mdoc_args(m, line, pos, buf, tok, &p);
+		if (ARGS_PEND == ac) {
+			if (ARGS_PPHRASE == lac)
+				ac = ARGS_PPHRASE;
+			else
+				ac = ARGS_PHRASE;
+		}
 
-		if (ARGS_ERROR == c)
-			return(0);
-		if (ARGS_EOLN == c)
-			break;
-		if (ARGS_PHRASE == c) {
-			assert(dohead);
-			if (reopen && ! mdoc_head_alloc(m, line, ppos, tok))
+		/* Don't emit leading punct. for phrases. */
+
+		if (NULL == head && 
+				ARGS_PHRASE != ac &&
+				ARGS_PPHRASE != ac &&
+				ARGS_QWORD != ac &&
+				DELIM_OPEN == mdoc_isdelim(p)) {
+			if ( ! mdoc_word_alloc(m, line, la, p))
 				return(0);
-			/*
-			 * Phrases are self-contained macro phrases used
-			 * in the columnar output of a macro. They need
-			 * special handling.
-			 */
-			if ( ! phrase(m, line, lastarg, buf))
+			continue;
+		}
+
+		/* Always re-open head for phrases. */
+
+		if (NULL == head || 
+				ARGS_PHRASE == ac || 
+				ARGS_PPHRASE == ac) {
+			if ( ! mdoc_head_alloc(m, line, ppos, tok))
 				return(0);
+			head = m->last;
+		}
+
+		if (ARGS_PHRASE == ac || ARGS_PPHRASE == ac) {
+			if (ARGS_PPHRASE == ac)
+				m->flags |= MDOC_PPHRASE;
+			if ( ! phrase(m, line, la, buf, ac))
+				return(0);
+			m->flags &= ~MDOC_PPHRASE;
 			if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
 				return(0);
-
-			reopen = 1;
 			continue;
 		}
 
-		if (MDOC_MAX == (c = lookup(tok, p))) {
-			if ( ! mdoc_word_alloc(m, line, lastarg, p))
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! mdoc_word_alloc(m, line, la, p))
 				return(0);
 			continue;
-		} 
+		}
 
-		if ( ! mdoc_macro(m, c, line, lastarg, pos, buf))
+		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 (1 == ppos && ! append_delims(m, line, pos, buf))
+	if (nl && ! append_delims(m, line, pos, buf))
 		return(0);
 
-	/* If the body's already open, then just return. */
-	if (0 == dohead) 
+	/* If we've already opened our body, exit now. */
+
+	if (NULL != body)
 		return(1);
 
+#ifdef	UGLY
+	/*
+	 * 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)) {
+			assert( ! (MDOC_ACTED & n->flags));
+			n->pending = head;
+			return(1);
+		}
+	}
+#endif
+
+	/* 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))
@@ -962,67 +1091,126 @@ blk_full(MACRO_PROT_ARGS)
 static int
 blk_part_imp(MACRO_PROT_ARGS)
 {
-	int		  la, c;
+	int		  la, nl;
+	enum mdoct	  ntok;
+	enum margserr	  ac;
 	char		 *p;
-	struct mdoc_node *blk, *body, *n;
+	struct mdoc_node *blk; /* saved block context */
+	struct mdoc_node *body; /* saved body context */
+	struct mdoc_node *n;
+
+	nl = MDOC_NEWLINE & m->flags;
 
-	/* If applicable, close out prior scopes. */
+	/*
+	 * 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);
-	/* Saved for later close-out. */
+
 	blk = m->last;
+
 	if ( ! mdoc_head_alloc(m, line, ppos, tok))
 		return(0);
 	if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
 		return(0);
-	if ( ! mdoc_body_alloc(m, line, ppos, tok))
-		return(0);
-	/* Saved for later close-out. */
-	body = m->last;
 
-	/* Body argument processing. */
+	/*
+	 * Open the body scope "on-demand", that is, after we've
+	 * processed all our the leading delimiters (open parenthesis,
+	 * etc.).
+	 */
 
-	for (;;) {
+	for (body = NULL; ; ) {
 		la = *pos;
-		c = mdoc_args(m, line, pos, buf, tok, &p);
-		assert(ARGS_PHRASE != c);
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
 
-		if (ARGS_ERROR == c)
+		if (ARGS_ERROR == ac)
 			return(0);
-		if (ARGS_PUNCT == c)
+		if (ARGS_EOLN == ac)
 			break;
-		if (ARGS_EOLN == c)
+		if (ARGS_PUNCT == ac)
 			break;
 
-		if (MDOC_MAX == (c = lookup(tok, p))) {
+		if (NULL == body && ARGS_QWORD != ac &&
+		    DELIM_OPEN == mdoc_isdelim(p)) {
 			if ( ! mdoc_word_alloc(m, line, la, p))
 				return(0);
 			continue;
 		} 
 
-		if ( ! mdoc_macro(m, c, line, la, pos, buf))
+		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 ( ! mdoc_word_alloc(m, line, la, p))
+				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)))
+			n->flags |= MDOC_EOS;
+
+	/* Up-propogate the end-of-space flag. */
+
+	if (n && (MDOC_EOS & n->flags)) {
+		body->flags |= MDOC_EOS;
+		body->parent->flags |= MDOC_EOS;
+	}
+
 	/* 
 	 * 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--XXX, deprecate in time.
+	 * crufty use of `Op' breakage.
+	 *
+	 * FIXME - this should be ifdef'd OpenBSD?
 	 */
 	for (n = m->last; n; n = n->parent)
 		if (body == n)
 			break;
-	if (NULL == n && ! mdoc_nwarn(m, body, EIMPBRK))
+
+	if (NULL == n && ! mdoc_nmsg(m, body, MANDOCERR_SCOPE))
 		return(0);
+
 	if (n && ! rew_last(m, body))
 		return(0);
 
 	/* Standard appending of delimiters. */
 
-	if (1 == ppos && ! append_delims(m, line, pos, buf))
+	if (nl && ! append_delims(m, line, pos, buf))
 		return(0);
 
 	/* Rewind scope, if applicable. */
@@ -1037,117 +1225,133 @@ blk_part_imp(MACRO_PROT_ARGS)
 static int
 blk_part_exp(MACRO_PROT_ARGS)
 {
-	int		  la, flushed, j, c, maxargs;
+	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;
 
-	/* Number of head arguments.  Only `Eo' has these, */
+	nl = MDOC_NEWLINE & m->flags;
 
-	switch (tok) {
-	case (MDOC_Eo):
-		maxargs = 1;
-		break;
-	default:
-		maxargs = 0;
-		break;
-	}
-
-	/* Begin the block scope. */
+	/*
+	 * 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); 
 
-	/* 
-	 * If no head arguments, open and then close out a head, noting
-	 * that we've flushed our terms.  `flushed' means that we've
-	 * flushed out the head and the body is open.
-	 */
+	for (head = body = NULL; ; ) {
+		la = *pos;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
 
-	if (0 == maxargs) {
-		if ( ! mdoc_head_alloc(m, line, ppos, tok))
-			return(0);
-		if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+		if (ARGS_ERROR == ac)
 			return(0);
-		if ( ! mdoc_body_alloc(m, line, ppos, tok))
-			return(0);
-		flushed = 1;
-	} else {
-		if ( ! mdoc_head_alloc(m, line, ppos, tok))
-			return(0);
-		flushed = 0;
-	}
+		if (ARGS_PUNCT == ac)
+			break;
+		if (ARGS_EOLN == ac)
+			break;
 
-	/* Process the head/head+body line arguments. */
+		/* Flush out leading punctuation. */
 
-	for (j = 0; ; j++) {
-		la = *pos;
-		if (j == maxargs && ! flushed) {
-			if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+		if (NULL == head && ARGS_QWORD != ac &&
+		    DELIM_OPEN == mdoc_isdelim(p)) {
+			assert(NULL == body);
+			if ( ! mdoc_word_alloc(m, line, la, p))
 				return(0);
-			flushed = 1;
-			if ( ! mdoc_body_alloc(m, line, ppos, tok))
+			continue;
+		} 
+
+		if (NULL == head) {
+			assert(NULL == body);
+			if ( ! mdoc_head_alloc(m, line, ppos, tok))
 				return(0);
+			head = m->last;
 		}
 
-		c = mdoc_args(m, line, pos, buf, tok, &p);
-		assert(ARGS_PHRASE != c);
-
-		if (ARGS_ERROR == c)
-			return(0);
-		if (ARGS_PUNCT == c)
-			break;
-		if (ARGS_EOLN == c)
-			break;
+		/*
+		 * `Eo' gobbles any data into the head, but most other
+		 * macros just immediately close out and begin the body.
+		 */
 
-		if (MDOC_MAX != (c = lookup(tok, p))) {
-			if ( ! flushed) {
-				if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
+		if (NULL == body) {
+			assert(head);
+			/* No check whether it's a macro! */
+			if (MDOC_Eo == tok)
+				if ( ! mdoc_word_alloc(m, line, la, p))
 					return(0);
-				flushed = 1;
-				if ( ! mdoc_body_alloc(m, line, ppos, tok))
-					return(0);
-			}
-			if ( ! mdoc_macro(m, c, line, la, pos, buf))
-				return(0);
-			break;
-		}
 
-		if ( ! flushed && mdoc_isdelim(p)) {
 			if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
 				return(0);
-			flushed = 1;
 			if ( ! mdoc_body_alloc(m, line, ppos, tok))
 				return(0);
+			body = m->last;
+
+			if (MDOC_Eo == tok)
+				continue;
 		}
-	
-		if ( ! mdoc_word_alloc(m, line, la, p))
+
+		assert(NULL != head && NULL != body);
+
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
+
+		if (MDOC_MAX == ntok) {
+			if ( ! mdoc_word_alloc(m, line, la, p))
+				return(0);
+			continue;
+		}
+
+		if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
 			return(0);
+		break;
 	}
 
-	/* Close the head and open the body, if applicable. */
+	/* Clean-up to leave in a consistent state. */
+
+	if (NULL == head) {
+		if ( ! mdoc_head_alloc(m, line, ppos, tok))
+			return(0);
+		head = m->last;
+	}
 
-	if ( ! flushed) {
+	if (NULL == body) {
 		if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos))
 			return(0);
 		if ( ! mdoc_body_alloc(m, line, ppos, tok))
 			return(0);
+		body = m->last;
 	}
 
 	/* Standard appending of delimiters. */
 
-	if (ppos > 1)
+	if ( ! nl)
 		return(1);
 	return(append_delims(m, line, pos, buf));
 }
 
 
+/* ARGSUSED */
 static int
 in_line_argn(MACRO_PROT_ARGS)
 {
-	int		  la, flushed, j, c, maxargs;
-	struct mdoc_arg	 *arg;
-	char		 *p;
+	int		 la, flushed, j, maxargs, nl;
+	enum margserr	 ac;
+	enum margverr	 av;
+	struct mdoc_arg	*arg;
+	char		*p;
+	enum mdoct	 ntok;
 
-	/* Fixed maximum arguments per macro, if applicable. */
+	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):
@@ -1159,40 +1363,52 @@ in_line_argn(MACRO_PROT_ARGS)
 	case (MDOC_Ux):
 		maxargs = 0;
 		break;
+	case (MDOC_Xr):
+		maxargs = 2;
+		break;
 	default:
 		maxargs = 1;
 		break;
 	}
 
-	/* Macro argument processing. */
-
-	for (arg = NULL;; ) {
+	for (arg = NULL; ; ) {
 		la = *pos;
-		c = mdoc_argv(m, line, tok, &arg, pos, buf);
+		av = mdoc_argv(m, line, tok, &arg, pos, buf);
 
-		if (ARGV_WORD == c) {
+		if (ARGV_WORD == av) {
 			*pos = la;
 			break;
 		} 
 
-		if (ARGV_EOLN == c)
+		if (ARGV_EOLN == av)
 			break;
-		if (ARGV_ARG == c)
+		if (ARGV_ARG == av)
 			continue;
 
 		mdoc_argv_free(arg);
 		return(0);
 	}
 
-	/* Open the element scope. */
-
-	if ( ! mdoc_elem_alloc(m, line, ppos, tok, arg))
-		return(0);
+	for (flushed = j = 0; ; ) {
+		la = *pos;
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
 
-	/* Process element arguments. */
+		if (ARGS_ERROR == ac)
+			return(0);
+		if (ARGS_PUNCT == ac)
+			break;
+		if (ARGS_EOLN == ac)
+			break;
 
-	for (flushed = j = 0; ; j++) {
-		la = *pos;
+		if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) && 
+				ARGS_QWORD != ac &&
+				0 == j && DELIM_OPEN == mdoc_isdelim(p)) {
+			if ( ! mdoc_word_alloc(m, line, la, p))
+				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))
@@ -1200,41 +1416,56 @@ in_line_argn(MACRO_PROT_ARGS)
 			flushed = 1;
 		}
 
-		c = mdoc_args(m, line, pos, buf, tok, &p);
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
-		if (ARGS_ERROR == c)
-			return(0);
-		if (ARGS_PUNCT == c)
-			break;
-		if (ARGS_EOLN == c)
-			break;
-
-		if (MDOC_MAX != (c = lookup(tok, p))) {
+		if (MDOC_MAX != ntok) {
 			if ( ! flushed && ! rew_elem(m, tok))
 				return(0);
 			flushed = 1;
-			if ( ! mdoc_macro(m, c, line, la, pos, buf))
+			if ( ! mdoc_macro(m, ntok, line, la, pos, buf))
 				return(0);
+			j++;
 			break;
 		}
 
 		if ( ! (MDOC_IGNDELIM & mdoc_macros[tok].flags) &&
-				! flushed && mdoc_isdelim(p)) {
+				ARGS_QWORD != ac &&
+				! flushed &&
+				DELIM_NONE != mdoc_isdelim(p)) {
 			if ( ! rew_elem(m, tok))
 				return(0);
 			flushed = 1;
 		}
-	
+
+		/* 
+		 * XXX: this is a hack to work around groff's ugliness
+		 * as regards `Xr' and extraneous arguments.  It should
+		 * ideally be deprecated behaviour, but because this is
+		 * code is no here, it's unlikely to be removed.
+		 */
+
+#ifdef __OpenBSD__
+		if (MDOC_Xr == tok && j == maxargs) {
+			if ( ! mdoc_elem_alloc(m, line, la, MDOC_Ns, NULL))
+				return(0);
+			if ( ! rew_elem(m, MDOC_Ns))
+				return(0);
+		}
+#endif
+
 		if ( ! mdoc_word_alloc(m, line, la, p))
 			return(0);
+		j++;
 	}
 
-	/* Close out and append delimiters. */
+	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 (ppos > 1)
+	if ( ! nl)
 		return(1);
 	return(append_delims(m, line, pos, buf));
 }
@@ -1243,9 +1474,12 @@ in_line_argn(MACRO_PROT_ARGS)
 static int
 in_line_eoln(MACRO_PROT_ARGS)
 {
-	int		  c, w, la;
-	struct mdoc_arg	 *arg;
-	char		 *p;
+	int		 la;
+	enum margserr	 ac;
+	enum margverr	 av;
+	struct mdoc_arg	*arg;
+	char		*p;
+	enum mdoct	 ntok;
 
 	assert( ! (MDOC_PARSED & mdoc_macros[tok].flags));
 
@@ -1253,15 +1487,15 @@ in_line_eoln(MACRO_PROT_ARGS)
 
 	for (arg = NULL; ; ) {
 		la = *pos;
-		c = mdoc_argv(m, line, tok, &arg, pos, buf);
+		av = mdoc_argv(m, line, tok, &arg, pos, buf);
 
-		if (ARGV_WORD == c) {
+		if (ARGV_WORD == av) {
 			*pos = la;
 			break;
 		}
-		if (ARGV_EOLN == c) 
+		if (ARGV_EOLN == av) 
 			break;
-		if (ARGV_ARG == c)
+		if (ARGV_ARG == av)
 			continue;
 
 		mdoc_argv_free(arg);
@@ -1277,23 +1511,24 @@ in_line_eoln(MACRO_PROT_ARGS)
 
 	for (;;) {
 		la = *pos;
-		w = mdoc_args(m, line, pos, buf, tok, &p);
+		ac = mdoc_args(m, line, pos, buf, tok, &p);
 
-		if (ARGS_ERROR == w)
+		if (ARGS_ERROR == ac)
 			return(0);
-		if (ARGS_EOLN == w)
+		if (ARGS_EOLN == ac)
 			break;
 
-		c = ARGS_QWORD == w ? MDOC_MAX : lookup(tok, p);
+		ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p);
 
-		if (MDOC_MAX != c) {
-			if ( ! rew_elem(m, tok))
+		if (MDOC_MAX == ntok) {
+			if ( ! mdoc_word_alloc(m, line, la, p))
 				return(0);
-			return(mdoc_macro(m, c, line, la, pos, buf));
-		} 
+			continue;
+		}
 
-		if ( ! mdoc_word_alloc(m, line, la, p))
+		if ( ! rew_elem(m, tok))
 			return(0);
+		return(mdoc_macro(m, ntok, line, la, pos, buf));
 	}
 
 	/* Close out (no delimiters). */
@@ -1302,12 +1537,38 @@ in_line_eoln(MACRO_PROT_ARGS)
 }
 
 
+/* 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 (SEC_SYNOPSIS != m->lastsec)
+		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.
+	 */
+
+	return(blk_part_imp(m, tok, line, ppos, pos, buf));
+}
+
+
 /* ARGSUSED */
 static int
 obsolete(MACRO_PROT_ARGS)
 {
 
-	return(mdoc_pwarn(m, line, ppos, EOBS));
+	return(mdoc_pmsg(m, line, ppos, MANDOCERR_MACROOBS));
 }
 
 
@@ -1317,32 +1578,36 @@ obsolete(MACRO_PROT_ARGS)
  * macro is encountered.
  */
 static int
-phrase(struct mdoc *m, int line, int ppos, char *buf)
+phrase(struct mdoc *m, int line, int ppos, char *buf, enum margserr ac)
 {
-	int		  c, w, la, pos;
-	char		 *p;
+	int		 la, pos;
+	enum margserr	 aac;
+	enum mdoct	 ntok;
+	char		*p;
+
+	assert(ARGS_PHRASE == ac || ARGS_PPHRASE == ac);
 
 	for (pos = ppos; ; ) {
 		la = pos;
 
-		/* Note: no calling context! */
-		w = mdoc_zargs(m, line, &pos, buf, 0, &p);
+		aac = mdoc_zargs(m, line, &pos, buf, 0, &p);
 
-		if (ARGS_ERROR == w)
+		if (ARGS_ERROR == aac)
 			return(0);
-		if (ARGS_EOLN == w)
+		if (ARGS_EOLN == aac)
 			break;
 
-		c = ARGS_QWORD == w ? MDOC_MAX : lookup_raw(p);
+		ntok = ARGS_QWORD == aac ? MDOC_MAX : lookup_raw(p);
 
-		if (MDOC_MAX != c) {
-			if ( ! mdoc_macro(m, c, line, la, &pos, buf))
+		if (MDOC_MAX == ntok) {
+			if ( ! mdoc_word_alloc(m, line, la, p))
 				return(0);
-			return(append_delims(m, line, &pos, buf));
-		} 
+			continue;
+		}
 
-		if ( ! mdoc_word_alloc(m, line, la, p))
+		if ( ! mdoc_macro(m, ntok, line, la, &pos, buf))
 			return(0);
+		return(append_delims(m, line, &pos, buf));
 	}
 
 	return(1);