X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/1afd14360246a3a49ced3c53ac6fae48ece8a0dc..a2320c4fc025a6262bc6cfa6e420d85fe8d60bd6:/macro.c

diff --git a/macro.c b/macro.c
index d39a1c67..4c24c01f 100644
--- a/macro.c
+++ b/macro.c
@@ -1,4 +1,4 @@
-/* $Id: macro.c,v 1.14 2008/12/30 18:15:26 kristaps Exp $ */
+/* $Id: macro.c,v 1.40 2009/01/17 16:15:27 kristaps Exp $ */
 /*
  * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
  *
@@ -28,841 +28,933 @@
 #include "private.h"
 
 /* FIXME: maxlineargs should be per LINE, no per TOKEN. */
-/* FIXME: prologue check should be in macro_call. */
 
-#define	_CC(p)	((const char **)p)
+static	int	  rewind_alt(int);
+static	int	  rewind_dohalt(int, enum mdoc_type, 
+			const struct mdoc_node *);
+#define	REWIND_REWIND	(1 << 0)
+#define	REWIND_NOHALT	(1 << 1)
+#define	REWIND_HALT	(1 << 2)
+static	int	  rewind_dobreak(int, enum mdoc_type, 
+			const struct mdoc_node *);
 
-static	int	  scope_rewind_exp(struct mdoc *, int, int, int);
-static	int	  scope_rewind_imp(struct mdoc *, int, int);
-static	int	  append_text(struct mdoc *, int, 
-			int, int, char *[]);
-static	int	  append_const(struct mdoc *, int, int, int, char *[]);
-static	int	  append_constarg(struct mdoc *, int, int, 
-			 int, const struct mdoc_arg *);
-static	int	  append_scoped(struct mdoc *, int, int, int, 
-			const char *[], int, const struct mdoc_arg *);
-static	int	  append_delims(struct mdoc *, int, int *, char *);
 
-
-static int
-append_delims(struct mdoc *mdoc, int tok, int *pos, char *buf)
-{
-	int		 c, lastarg;
-	char		*p;
-
-	if (0 == buf[*pos])
-		return(1);
-
-	mdoc_msg(mdoc, *pos, "`%s' flushing punctuation",
-			mdoc_macronames[tok]);
-
-	for (;;) {
-		lastarg = *pos;
-		c = mdoc_args(mdoc, tok, pos, buf, 0, &p);
-		if (ARGS_ERROR == c)
-			return(0);
-		else if (ARGS_EOLN == c)
-			break;
-		assert(mdoc_isdelim(p));
-		mdoc_word_alloc(mdoc, lastarg, p);
-	}
-
-	return(1);
-}
+static	int	  rewind_elem(struct mdoc *, int);
+static	int	  rewind_impblock(struct mdoc *, int, int, int);
+static	int	  rewind_expblock(struct mdoc *, int, int, int);
+static	int	  rewind_subblock(enum mdoc_type, struct mdoc *, int);
+static	int	  rewind_last(int, int, 
+			struct mdoc *, struct mdoc_node *);
+static	int	  append_delims(struct mdoc *, 
+			int, int, int *, char *);
+static	int	  lookup(struct mdoc *, int, int, int, const char *);
 
 
 static int
-scope_rewind_imp(struct mdoc *mdoc, int ppos, int tok)
+lookup(struct mdoc *mdoc, int line, int pos, int from, const char *p)
 {
-	struct mdoc_node *n;
-	int		  t;
-
-	n = mdoc->last ? mdoc->last->parent : NULL;
-
-	/* LINTED */
-	for ( ; n; n = n->parent) {
-		if (MDOC_BLOCK != n->type) 
-			continue;
-		if (tok == (t = n->data.block.tok))
-			break;
-		if ( ! (MDOC_EXPLICIT & mdoc_macros[t].flags))
-			continue;
-		return(mdoc_err(mdoc, tok, ppos, ERR_SCOPE_BREAK));
-	}
+	int		 res;
 
-	if (n) {
-		mdoc->last = n;
-		mdoc_msg(mdoc, ppos, "scope: rewound implicit `%s'",
-				mdoc_macronames[tok]);
-		return(1);
-	} 
+	res = mdoc_find(mdoc, p);
+	if (MDOC_PARSED & mdoc_macros[from].flags)
+		return(res);
+	if (MDOC_MAX == res)
+		return(res);
 
-	mdoc_msg(mdoc, ppos, "scope: new implicit `%s'", 
-			mdoc_macronames[tok]);
-	return(1);
+	if ( ! mdoc_pwarn(mdoc, line, pos, WARN_SYNTAX, "macro-like parameter"))
+		return(-1);
+	return(MDOC_MAX);
 }
 
 
 static int
-scope_rewind_exp(struct mdoc *mdoc, int ppos, int tok, int dst)
+rewind_last(int tok, int type, struct mdoc *mdoc, struct mdoc_node *to)
 {
-	struct mdoc_node *n;
 
-	assert(mdoc->last);
-
-	/* LINTED */
-	for (n = mdoc->last->parent; n; n = n->parent) {
-		if (MDOC_BLOCK != n->type) 
-			continue;
-		if (dst == n->data.block.tok)
-			break;
-		return(mdoc_err(mdoc, tok, ppos, ERR_SCOPE_BREAK));
+	assert(to);
+	mdoc->next = MDOC_NEXT_SIBLING;
+	if (mdoc->last == to) {
+		if ( ! mdoc_valid_post(mdoc))
+			return(0);
+		if ( ! mdoc_action_post(mdoc))
+			return(0);
+		mdoc_msg(mdoc, "rewound %s %s to %s %s", 
+				mdoc_type2a(type),
+				mdoc_macronames[tok],
+				mdoc_type2a(mdoc->last->type),
+				mdoc_macronames[mdoc->last->tok]);
+		return(1);
 	}
 
-	if (NULL == (mdoc->last = n))
-		return(mdoc_err(mdoc, tok, ppos, ERR_SCOPE_NOCTX));
-
-	mdoc_msg(mdoc, ppos, "scope: rewound explicit `%s' to `%s'",
-			mdoc_macronames[tok], mdoc_macronames[dst]);
+	do {
+		mdoc->last = mdoc->last->parent;
+		assert(mdoc->last);
+		if ( ! mdoc_valid_post(mdoc))
+			return(0);
+		if ( ! mdoc_action_post(mdoc))
+			return(0);
+		mdoc_msg(mdoc, "rewound %s %s to %s %s",
+				mdoc_type2a(type),
+				mdoc_macronames[tok],
+				mdoc_type2a(mdoc->last->type),
+				mdoc_macronames[mdoc->last->tok]);
+	} while (mdoc->last != to);
 
 	return(1);
 }
 
 
 static int
-append_constarg(struct mdoc *mdoc, int tok, int pos, 
-		int argc, const struct mdoc_arg *argv)
+rewind_alt(int tok)
 {
-
 	switch (tok) {
+	case (MDOC_Ac):
+		return(MDOC_Ao);
+	case (MDOC_Bc):
+		return(MDOC_Bo);
+	case (MDOC_Dc):
+		return(MDOC_Do);
+	case (MDOC_Ec):
+		return(MDOC_Eo);
+	case (MDOC_Ed):
+		return(MDOC_Bd);
+	case (MDOC_Ef):
+		return(MDOC_Bf);
+	case (MDOC_Ek):
+		return(MDOC_Bk);
+	case (MDOC_El):
+		return(MDOC_Bl);
+	case (MDOC_Fc):
+		return(MDOC_Fo);
+	case (MDOC_Oc):
+		return(MDOC_Oo);
+	case (MDOC_Pc):
+		return(MDOC_Po);
+	case (MDOC_Qc):
+		return(MDOC_Qo);
+	case (MDOC_Re):
+		return(MDOC_Rs);
+	case (MDOC_Sc):
+		return(MDOC_So);
+	case (MDOC_Xc):
+		return(MDOC_Xo);
 	default:
 		break;
 	}
-
-	mdoc_elem_alloc(mdoc, pos, tok, argc, argv, 0, NULL);
-	return(1);
+	abort();
+	/* NOTREACHED */
 }
 
 
-/*
- * Append a node with implicit or explicit scoping ONLY.  ALL macros
- * with the implicit- or explicit-scope callback must be included here.
- */
 static int
-append_scoped(struct mdoc *mdoc, int tok, int pos, 
-		int sz, const char *args[], 
-		int argc, const struct mdoc_arg *argv)
+rewind_dohalt(int tok, enum mdoc_type type, const struct mdoc_node *p)
 {
-	enum mdoc_sec	  sec;
 
-	if ( ! mdoc_valid(mdoc, tok, pos, sz, args, argc, argv))
-		return(0);
+	if (MDOC_ROOT == p->type)
+		return(REWIND_HALT);
+	if (MDOC_TEXT == p->type) 
+		return(REWIND_NOHALT);
+	if (MDOC_ELEM == p->type) 
+		return(REWIND_NOHALT);
 
 	switch (tok) {
+	/* One-liner implicit-scope. */
+	case (MDOC_Aq):
+		/* FALLTHROUGH */
+	case (MDOC_Bq):
+		/* FALLTHROUGH */
+	case (MDOC_D1):
+		/* FALLTHROUGH */
+	case (MDOC_Dl):
+		/* FALLTHROUGH */
+	case (MDOC_Dq):
+		/* FALLTHROUGH */
+	case (MDOC_Op):
+		/* FALLTHROUGH */
+	case (MDOC_Pq):
+		/* FALLTHROUGH */
+	case (MDOC_Ql):
+		/* FALLTHROUGH */
+	case (MDOC_Qq):
+		/* FALLTHROUGH */
+	case (MDOC_Sq):
+		assert(MDOC_BODY != type);
+		assert(MDOC_TAIL != type);
+		if (type == p->type && tok == p->tok)
+			return(REWIND_REWIND);
+		break;
+
+	/* Multi-line implicit-scope. */
+	case (MDOC_It):
+		assert(MDOC_TAIL != type);
+		if (type == p->type && tok == p->tok)
+			return(REWIND_REWIND);
+		if (MDOC_BODY == p->type && MDOC_Bl == p->tok)
+			return(REWIND_HALT);
+		break;
 	case (MDOC_Sh):
-		sec = mdoc_atosec((size_t)sz, _CC(args));
-		if (SEC_CUSTOM != sec)
-			mdoc->sec_lastn = sec;
-		mdoc->sec_last = sec;
+		if (type == p->type && tok == p->tok)
+			return(REWIND_REWIND);
 		break;
-	default:
+	case (MDOC_Ss):
+		assert(MDOC_TAIL != type);
+		if (type == p->type && tok == p->tok)
+			return(REWIND_REWIND);
+		if (MDOC_BODY == p->type && MDOC_Sh == p->tok)
+			return(REWIND_HALT);
 		break;
+	
+	/* Multi-line explicit scope start. */
+	case (MDOC_Ao):
+		/* FALLTHROUGH */
+	case (MDOC_Bd):
+		/* FALLTHROUGH */
+	case (MDOC_Bf):
+		/* FALLTHROUGH */
+	case (MDOC_Bk):
+		/* FALLTHROUGH */
+	case (MDOC_Bl):
+		/* FALLTHROUGH */
+	case (MDOC_Bo):
+		/* FALLTHROUGH */
+	case (MDOC_Do):
+		/* FALLTHROUGH */
+	case (MDOC_Eo):
+		/* FALLTHROUGH */
+	case (MDOC_Fo):
+		/* FALLTHROUGH */
+	case (MDOC_Oo):
+		/* FALLTHROUGH */
+	case (MDOC_Po):
+		/* FALLTHROUGH */
+	case (MDOC_Qo):
+		/* FALLTHROUGH */
+	case (MDOC_Rs):
+		/* FALLTHROUGH */
+	case (MDOC_So):
+		/* FALLTHROUGH */
+	case (MDOC_Xo):
+		if (type == p->type && tok == p->tok)
+			return(REWIND_REWIND);
+		break;
+
+	/* Multi-line explicit scope close. */
+	case (MDOC_Ac):
+		/* FALLTHROUGH */
+	case (MDOC_Bc):
+		/* FALLTHROUGH */
+	case (MDOC_Dc):
+		/* FALLTHROUGH */
+	case (MDOC_Ec):
+		/* FALLTHROUGH */
+	case (MDOC_Ed):
+		/* FALLTHROUGH */
+	case (MDOC_Ek):
+		/* FALLTHROUGH */
+	case (MDOC_El):
+		/* FALLTHROUGH */
+	case (MDOC_Fc):
+		/* FALLTHROUGH */
+	case (MDOC_Ef):
+		/* FALLTHROUGH */
+	case (MDOC_Oc):
+		/* FALLTHROUGH */
+	case (MDOC_Pc):
+		/* FALLTHROUGH */
+	case (MDOC_Qc):
+		/* FALLTHROUGH */
+	case (MDOC_Re):
+		/* FALLTHROUGH */
+	case (MDOC_Sc):
+		/* FALLTHROUGH */
+	case (MDOC_Xc):
+		if (type == p->type && rewind_alt(tok) == p->tok)
+			return(REWIND_REWIND);
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
 	}
 
-	mdoc_block_alloc(mdoc, pos, tok, (size_t)argc, argv);
-	mdoc_head_alloc(mdoc, pos, tok, (size_t)sz, _CC(args));
-	mdoc_body_alloc(mdoc, pos, tok);
-	return(1);
+	return(REWIND_NOHALT);
 }
 
 
 static int
-append_const(struct mdoc *mdoc, int tok, 
-		int pos, int sz, char *args[])
+rewind_dobreak(int tok, enum mdoc_type type, const struct mdoc_node *p)
 {
 
-	if ( ! mdoc_valid(mdoc, tok, pos, sz, _CC(args), 0, NULL))
-		return(0);
+	assert(MDOC_ROOT != p->type);
+	if (MDOC_ELEM == p->type)
+		return(1);
+	if (MDOC_TEXT == p->type)
+		return(1);
 
 	switch (tok) {
-	case (MDOC_At):
-		if (0 == sz)
-			break;
-
-		if (ATT_DEFAULT != mdoc_atoatt(args[0])) {
-			mdoc_elem_alloc(mdoc, pos, tok, 0, 
-					NULL, 1, _CC(&args[0]));
-		} else {
-			mdoc_elem_alloc(mdoc, pos, tok, 
-					0, NULL, 0, NULL);
-			mdoc_word_alloc(mdoc, pos, args[0]);
-		}
+	/* Implicit rules. */
+	case (MDOC_It):
+		return(MDOC_It == p->tok);
+	case (MDOC_Ss):
+		return(MDOC_Ss == p->tok);
+	case (MDOC_Sh):
+		if (MDOC_Ss == p->tok)
+			return(1);
+		return(MDOC_Sh == p->tok);
 
-		if (1 == sz)
+	/* Extra scope rules. */
+	case (MDOC_El):
+		if (MDOC_It == p->tok)
 			return(1);
-		mdoc_word_alloc(mdoc, pos, args[1]);
-		return(1);
+		break;
 	default:
 		break;
 	}
 
-	mdoc_elem_alloc(mdoc, pos, tok, 0, NULL, (size_t)sz, _CC(args));
-	return(1);
+	if (MDOC_EXPLICIT & mdoc_macros[tok].flags) 
+		return(p->tok == rewind_alt(tok));
+	else if (MDOC_BLOCK == p->type)
+		return(1);
+
+	return(tok == p->tok);
 }
 
 
 static int
-append_text(struct mdoc *mdoc, int tok, 
-		int pos, int sz, char *args[])
+rewind_elem(struct mdoc *mdoc, int tok)
 {
+	struct mdoc_node *n;
 
-	if ( ! mdoc_valid(mdoc, tok, pos, sz, _CC(args), 0, NULL))
-		return(0);
-	mdoc_elem_alloc(mdoc, pos, tok, 0, NULL, (size_t)sz, _CC(args));
-	return(1);
+	n = mdoc->last;
+	if (MDOC_ELEM != n->type)
+		n = n->parent;
+	assert(MDOC_ELEM == n->type);
+	assert(tok == n->tok);
+
+	return(rewind_last(tok, MDOC_ELEM, mdoc, n));
 }
 
 
-int
-macro_text(MACRO_PROT_ARGS)
+static int
+rewind_subblock(enum mdoc_type type, struct mdoc *mdoc, int tok)
 {
-	int		  lastarg, lastpunct, c, j;
-	char		 *args[MDOC_LINEARG_MAX];
-
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
+	struct mdoc_node *n;
+	int		  c;
 
-	/* Token pre-processing.  */
+	c = rewind_dohalt(tok, type, mdoc->last);
+	if (REWIND_HALT == c)
+		return(1);
+	if (REWIND_REWIND == c)
+		return(rewind_last(tok, type, mdoc, mdoc->last));
 
-	switch (tok) {
-	case (MDOC_Pp):
-		/* `.Pp' ignored when following `.Sh' or `.Ss'. */
-		assert(mdoc->last);
-		if (MDOC_BODY != mdoc->last->type)
-			break;
-		switch (mdoc->last->data.body.tok) {
-		case (MDOC_Ss):
-			/* FALLTHROUGH */
-		case (MDOC_Sh):
-			if ( ! mdoc_warn(mdoc, tok, ppos, WARN_IGN_AFTER_BLK))
-				return(0);
+	/* LINTED */
+	for (n = mdoc->last->parent; n; n = n->parent) {
+		c = rewind_dohalt(tok, type, n);
+		if (REWIND_HALT == c)
 			return(1);
-		default:
+		if (REWIND_REWIND == c)
 			break;
-		}
-		break;
-	default:
-		break;
+		else if (rewind_dobreak(tok, type, n))
+			continue;
+		return(mdoc_nerr(mdoc, n, "body scope broken"));
 	}
 
-	/* Process line parameters. */
-
-	j = 0;
-	lastarg = ppos;
-	lastpunct = 0;
+	assert(n);
+	return(rewind_last(tok, type, mdoc, n));
+}
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
 
-	/* 
-	 * Parse out the next argument, unquoted and unescaped.   If
-	 * we're a word (which may be punctuation followed eventually by
-	 * a real word), then fall into checking for callables.  If
-	 * only punctuation remains and we're the first, then flush
-	 * arguments, punctuation and exit; else, return to the caller.
-	 */
+static int
+rewind_expblock(struct mdoc *mdoc, int tok, int line, int ppos)
+{
+	struct mdoc_node *n;
+	int		  c;
 
-	lastarg = *pos;
+	c = rewind_dohalt(tok, MDOC_BLOCK, mdoc->last);
+	if (REWIND_HALT == c)
+		return(mdoc_perr(mdoc, line, ppos, "closing macro has no context"));
+	if (REWIND_REWIND == c)
+		return(rewind_last(tok, MDOC_BLOCK, mdoc, mdoc->last));
 
-	switch (mdoc_args(mdoc, tok, pos, buf, ARGS_DELIM, &args[j])) {
-	case (ARGS_ERROR):
-		return(0);
-	case (ARGS_WORD):
-		break;
-	case (ARGS_PUNCT):
-		if ( ! lastpunct && ! append_text(mdoc, tok, ppos, j, args))
-			return(0);
-		if (ppos > 1)
-			return(1);
-		return(append_delims(mdoc, tok, pos, buf));
-	case (ARGS_EOLN):
-		if (lastpunct)
-			return(1);
-		return(append_text(mdoc, tok, ppos, j, args));
-	default:
-		abort();
-		/* NOTREACHED */
+	/* LINTED */
+	for (n = mdoc->last->parent; n; n = n->parent) {
+		c = rewind_dohalt(tok, MDOC_BLOCK, n);
+		if (REWIND_HALT == c)
+			return(mdoc_perr(mdoc, line, ppos, "closing macro has no context"));
+		if (REWIND_REWIND == c)
+			break;
+		else if (rewind_dobreak(tok, MDOC_BLOCK, n))
+			continue;
+		return(mdoc_nerr(mdoc, n, "block scope broken"));
 	}
 
-	/* 
-	 * Command found.  First flush out arguments, then call the
-	 * command.  If we're the line macro when it exits, flush
-	 * terminal punctuation.
-	 */
+	assert(n);
+	return(rewind_last(tok, MDOC_BLOCK, mdoc, n));
+}
 
-	if (MDOC_MAX != (c = mdoc_find(mdoc, args[j]))) {
-		if ( ! lastpunct && ! append_text(mdoc, tok, ppos, j, args))
-			return(0);
-		if ( ! mdoc_macro(mdoc, c, lastarg, pos, buf))
-			return(0);
-		if (ppos > 1)
+
+static int
+rewind_impblock(struct mdoc *mdoc, int tok, int line, int ppos)
+{
+	struct mdoc_node *n;
+	int		  c;
+
+	c = rewind_dohalt(tok, MDOC_BLOCK, mdoc->last);
+	if (REWIND_HALT == c)
+		return(1);
+	if (REWIND_REWIND == c)
+		return(rewind_last(tok, MDOC_BLOCK, mdoc, mdoc->last));
+
+	/* LINTED */
+	for (n = mdoc->last->parent; n; n = n->parent) {
+		c = rewind_dohalt(tok, MDOC_BLOCK, n);
+		if (REWIND_HALT == c)
 			return(1);
-		return(append_delims(mdoc, tok, pos, buf));
+		else if (REWIND_REWIND == c)
+			break;
+		else if (rewind_dobreak(tok, MDOC_BLOCK, n))
+			continue;
+		return(mdoc_nerr(mdoc, n, "block scope broken"));
 	}
 
-	/* Word/non-term-punctuation found. */
+	assert(n);
+	return(rewind_last(tok, MDOC_BLOCK, mdoc, n));
+}
 
-	if ( ! mdoc_isdelim(args[j])) {
-		/* Words are appended to the array of arguments. */
-		j++;
-		lastpunct = 0;
-		goto again;
-	}
 
-	/* 
-	 * For punctuation, flush all collected words, then flush
-	 * punctuation, then start collecting again.   Of course, this
-	 * is non-terminal punctuation.
-	 */
+static int
+append_delims(struct mdoc *mdoc, int tok, 
+		int line, int *pos, char *buf)
+{
+	int		 c, lastarg;
+	char		*p;
 
-	if ( ! lastpunct && ! append_text(mdoc, tok, ppos, j, args))
-		return(0);
+	if (0 == buf[*pos])
+		return(1);
 
-	mdoc_word_alloc(mdoc, lastarg, args[j]);
-	j = 0;
-	lastpunct = 1;
+	for (;;) {
+		lastarg = *pos;
+		c = mdoc_args(mdoc, line, pos, buf, 0, &p);
+		if (ARGS_ERROR == c)
+			return(0);
+		else if (ARGS_EOLN == c)
+			break;
+		assert(mdoc_isdelim(p));
+		if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
+			return(0);
+		mdoc->next = MDOC_NEXT_SIBLING;
+	}
 
-	goto again;
-	/* NOTREACHED */
+	return(1);
 }
 
 
+/* ARGSUSED */
 int
-macro_prologue_dtitle(MACRO_PROT_ARGS)
+macro_scoped_close(MACRO_PROT_ARGS)
 {
-	int		  lastarg, j;
-	char		 *args[MDOC_LINEARG_MAX];
-
-	if (SEC_PROLOGUE != mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_NPROLOGUE));
-	if (0 == mdoc->meta.date)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE_OO));
-	if (mdoc->meta.title[0])
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE_REP));
+	int	 	 tt, j, c, lastarg, maxargs, flushed;
+	char		*p;
 
-	j = -1;
-	lastarg = ppos;
+	switch (tok) {
+	case (MDOC_Ec):
+		maxargs = 1;
+		break;
+	default:
+		maxargs = 0;
+		break;
+	}
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
+	tt = rewind_alt(tok);
 
-	lastarg = *pos;
+	mdoc_msg(mdoc, "parse-quiet: %s closing %s",
+			mdoc_macronames[tok], mdoc_macronames[tt]);
 
-	switch (mdoc_args(mdoc, tok, pos, buf, 0, &args[++j])) {
-	case (ARGS_EOLN):
-		if (mdoc->meta.title)
-			return(1);
-		if ( ! mdoc_warn(mdoc, tok, ppos, WARN_ARGS_GE1))
-			return(0);
-		(void)xstrlcpy(mdoc->meta.title, 
-				"UNTITLED", META_TITLE_SZ);
-		return(1);
-	case (ARGS_ERROR):
-		return(0);
-	default:
-		break;
+	if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) {
+		if (0 == buf[*pos]) {
+			if ( ! rewind_subblock(MDOC_BODY, mdoc, tok))
+				return(0);
+			return(rewind_expblock(mdoc, tok, line, ppos));
+		}
+		return(mdoc_perr(mdoc, line, ppos, "macro expects no parameters"));
 	}
 
-	if (MDOC_MAX != mdoc_find(mdoc, args[j]) && ! mdoc_warn
-			(mdoc, tok, lastarg, WARN_SYNTAX_MACLIKE))
+	if ( ! rewind_subblock(MDOC_BODY, mdoc, tok))
 		return(0);
 
-	if (0 == j) {
-		if (xstrlcpy(mdoc->meta.title, args[0], META_TITLE_SZ))
-			goto again;
-		return(mdoc_err(mdoc, tok, lastarg, ERR_SYNTAX_ARGFORM));
-
-	} else if (1 == j) {
-		mdoc->meta.msec = mdoc_atomsec(args[1]);
-		if (MSEC_DEFAULT != mdoc->meta.msec)
-			goto again;
-		return(mdoc_err(mdoc, tok, -1, ERR_SYNTAX_ARGFORM));
-
-	} else if (2 == j) {
-		mdoc->meta.vol = mdoc_atovol(args[2]);
-		if (VOL_DEFAULT != mdoc->meta.vol)
-			goto again;
-		mdoc->meta.arch = mdoc_atoarch(args[2]);
-		if (ARCH_DEFAULT != mdoc->meta.arch)
-			goto again;
-		return(mdoc_err(mdoc, tok, lastarg, ERR_SYNTAX_ARGFORM));
-	}
+	lastarg = ppos;
+	flushed = 0;
 
-	return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
-}
+	if (maxargs > 0) {
+		if ( ! mdoc_tail_alloc(mdoc, line, ppos, tt))
+			return(0);
+		mdoc->next = MDOC_NEXT_CHILD;
+	}
 
+	for (j = 0; j < MDOC_LINEARG_MAX; j++) {
+		lastarg = *pos;
 
-int
-macro_prologue_os(MACRO_PROT_ARGS)
-{
-	int		  lastarg, j;
-	char		 *args[MDOC_LINEARG_MAX];
+		if (j == maxargs && ! flushed) {
+			if ( ! rewind_expblock(mdoc, tok, line, ppos))
+				return(0);
+			flushed = 1;
+		}
 
-	if (SEC_PROLOGUE != mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_NPROLOGUE));
-	if (0 == mdoc->meta.title[0])
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE_OO));
-	if (mdoc->meta.os[0])
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE_REP));
+		c = mdoc_args(mdoc, line, pos, buf, ARGS_DELIM, &p);
+		if (ARGS_ERROR == c)
+			return(0);
+		if (ARGS_PUNCT == c)
+			break;
+		if (ARGS_EOLN == c)
+			break;
 
-	j = -1;
-	lastarg = ppos;
+		if (-1 == (c = lookup(mdoc, line, lastarg, tok, p)))
+			return(0);
+		else if (MDOC_MAX != c) {
+			if ( ! flushed) {
+				if ( ! rewind_expblock(mdoc, tok, line, ppos))
+					return(0);
+				flushed = 1;
+			}
+			if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf))
+				return(0);
+			break;
+		} 
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
+		if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
+			return(0);
+		mdoc->next = MDOC_NEXT_SIBLING;
+	}
 
-	lastarg = *pos;
+	if (MDOC_LINEARG_MAX == j)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
 
-	switch (mdoc_args(mdoc, tok, pos, buf, 
-				ARGS_QUOTED, &args[++j])) {
-	case (ARGS_EOLN):
-		mdoc->sec_lastn = mdoc->sec_last = SEC_BODY;
-		return(1);
-	case (ARGS_ERROR):
+	if ( ! flushed && ! rewind_expblock(mdoc, tok, line, ppos))
 		return(0);
-	default:
-		break;
-	}
-	
-	if ( ! xstrlcat(mdoc->meta.os, args[j], sizeof(mdoc->meta.os)))
-		return(mdoc_err(mdoc, tok, lastarg, ERR_SYNTAX_ARGFORM));
-	if ( ! xstrlcat(mdoc->meta.os, " ", sizeof(mdoc->meta.os)))
-		return(mdoc_err(mdoc, tok, lastarg, ERR_SYNTAX_ARGFORM));
 
-	goto again;
-	/* NOTREACHED */
+	if (ppos > 1)
+		return(1);
+	return(append_delims(mdoc, tok, line, pos, buf));
 }
 
 
+/*
+ * A general text domain macro.  When invoked, this opens a scope that
+ * accepts words until either end-of-line, only-punctuation, or a
+ * callable macro.  If the word is punctuation (not only-punctuation),
+ * then the scope is closed out, the punctuation appended, then the
+ * scope opened again.  If any terminating conditions are met, the scope
+ * is closed out.  If this is the first macro in the line and
+ * only-punctuation remains, this punctuation is flushed.
+ */
 int
-macro_prologue_ddate(MACRO_PROT_ARGS)
+macro_text(MACRO_PROT_ARGS)
 {
-	int		  lastarg, j;
-	char		 *args[MDOC_LINEARG_MAX], date[64];
-
-	if (SEC_PROLOGUE != mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_NPROLOGUE));
-	if (mdoc->meta.title[0])
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE_OO));
-	if (mdoc->meta.date)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE_REP));
-
-	j = -1;
-	date[0] = 0;
-	lastarg = ppos;
+	int		  la, lastpunct, c, sz, fl, argc;
+	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
+	char		 *p;
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
+	la = ppos;
+	lastpunct = 0;
 
-	lastarg = *pos;
-	switch (mdoc_args(mdoc, tok, pos, buf, 0, &args[++j])) {
-	case (ARGS_EOLN):
-		if (mdoc->meta.date)
-			return(1);
-		mdoc->meta.date = mdoc_atotime(date);
-		if (mdoc->meta.date)
-			return(1);
-		return(mdoc_err(mdoc, tok, ppos, ERR_SYNTAX_ARGFORM));
-	case (ARGS_ERROR):
+	for (argc = 0; argc < MDOC_LINEARG_MAX; argc++) {
+		la = *pos;
+
+		c = mdoc_argv(mdoc, line, tok, &argv[argc], pos, buf);
+		if (ARGV_EOLN == c)
+			break;
+		if (ARGV_WORD == c) {
+			*pos = la;
+			break;
+		} else if (ARGV_ARG == c)
+			continue;
+
+		mdoc_argv_free(argc, argv);
 		return(0);
-	default:
-		break;
 	}
-	
-	if (MDOC_MAX != mdoc_find(mdoc, args[j]) && ! mdoc_warn
-			(mdoc, tok, lastarg, WARN_SYNTAX_MACLIKE))
-		return(0);
-	
-	if (0 == j) {
-		if (xstrcmp("$Mdocdate: December 30 2008 $", args[j])) {
-			mdoc->meta.date = time(NULL);
-			goto again;
-		} else if (xstrcmp("$Mdocdate:", args[j])) 
-			goto again;
-	} else if (4 == j)
-		if ( ! xstrcmp("$", args[j]))
-			goto again;
-
-	if ( ! xstrlcat(date, args[j], sizeof(date)))
-		return(mdoc_err(mdoc, tok, lastarg, ERR_SYNTAX_ARGFORM));
-	if ( ! xstrlcat(date, " ", sizeof(date)))
-		return(mdoc_err(mdoc, tok, lastarg, ERR_SYNTAX_ARGFORM));
-
-	goto again;
-	/* NOTREACHED */
-}
 
+	if (MDOC_LINEARG_MAX == argc) {
+		mdoc_argv_free(argc, argv);
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
+	}
 
-int
-macro_scoped_explicit(MACRO_PROT_ARGS)
-{
-	int		  c, lastarg, j;
-	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
-	struct mdoc_node *n;
+	c = mdoc_elem_alloc(mdoc, line, ppos, tok, argc, argv);
 
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
+	if (0 == c) {
+		mdoc_argv_free(argc, argv);
+		return(0);
+	}
 
-	/*
-	 * First close out the explicit scope.  The `end' tags (such as
-	 * `.El' to `.Bl' don't cause anything to happen: we merely
-	 * readjust our last parse point.
-	 */
+	mdoc->next = MDOC_NEXT_CHILD;
 
-	switch (tok) {
-	case (MDOC_El):
-		return(scope_rewind_exp(mdoc, ppos, tok, MDOC_Bl));
-	case (MDOC_Ed):
-		return(scope_rewind_exp(mdoc, ppos, tok, MDOC_Bd));
-	default:
-		break;
-	}
+	fl = ARGS_DELIM;
+	if (MDOC_QUOTABLE & mdoc_macros[tok].flags)
+		fl |= ARGS_QUOTED;
 
-	assert(MDOC_EXPLICIT & mdoc_macros[tok].flags);
+	for (lastpunct = sz = 0; sz + argc < MDOC_LINEARG_MAX; sz++) {
+		la = *pos;
 
-	/* Token pre-processing. */
+		c = mdoc_args(mdoc, line, pos, buf, fl, &p);
+		if (ARGS_ERROR == c) {
+			mdoc_argv_free(argc, argv);
+			return(0);
+		}
 
-	switch (tok) {
-	case (MDOC_Bl):
-		/* FALLTHROUGH */
-	case (MDOC_Bd):
-		/* `.Pp' ignored when preceding `.Bl' or `.Bd'. */
-		assert(mdoc->last);
-		if (MDOC_ELEM != mdoc->last->type)
+		if (ARGS_EOLN == c)
 			break;
-		if (MDOC_Pp != mdoc->last->data.elem.tok)
+		if (ARGS_PUNCT == c)
 			break;
-		if ( ! mdoc_warn(mdoc, tok, ppos, WARN_IGN_BEFORE_BLK))
+
+		if (-1 == (c = lookup(mdoc, line, la, tok, p)))
 			return(0);
-		assert(mdoc->last->prev);
-		n = mdoc->last;
-		mdoc->last = mdoc->last->prev;
-		mdoc->last->next = NULL;
-		mdoc_node_free(n);
-		break;
-	default:
-		break;
-	}
+		else if (MDOC_MAX != c) {
+			if (0 == lastpunct && ! rewind_elem(mdoc, tok)) {
+				mdoc_argv_free(argc, argv);
+				return(0);
+			}
+			mdoc_argv_free(argc, argv);
 
-	lastarg = *pos;
+			c = mdoc_macro(mdoc, c, line, la, pos, buf);
+			if (0 == c)
+				return(0);
+			if (ppos > 1)
+				return(1);
+			return(append_delims(mdoc, tok, line, pos, buf));
+		}
 
-	for (j = 0; j < MDOC_LINEARG_MAX; j++) {
-		lastarg = *pos;
-		c = mdoc_argv(mdoc, tok, &argv[j], pos, buf);
-		if (0 == c)
-			break;
-		else if (1 == c)
-			continue;
+		if (mdoc_isdelim(p)) {
+			if (0 == lastpunct && ! rewind_elem(mdoc, tok)) {
+				mdoc_argv_free(argc, argv);
+				return(0);
+			}
+			lastpunct = 1;
+		} else if (lastpunct) {
+			c = mdoc_elem_alloc(mdoc, line, 
+					ppos, tok, argc, argv);
+			if (0 == c) {
+				mdoc_argv_free(argc, argv);
+				return(0);
+			}
+			mdoc->next = MDOC_NEXT_CHILD;
+			lastpunct = 0;
+		}
 
-		mdoc_argv_free(j, argv);
-		return(0);
+		if ( ! mdoc_word_alloc(mdoc, line, la, p))
+			return(0);
+		mdoc->next = MDOC_NEXT_SIBLING;
 	}
 
-	if (MDOC_LINEARG_MAX == j) {
-		mdoc_argv_free(j, argv);
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
-	}
+	mdoc_argv_free(argc, argv);
+
+	if (sz == MDOC_LINEARG_MAX)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
 
-	c = append_scoped(mdoc, tok, ppos, 0, NULL, j, argv);
-	mdoc_argv_free(j, argv);
-	return(c);
+	if (0 == lastpunct && ! rewind_elem(mdoc, tok))
+		return(0);
+	if (ppos > 1)
+		return(1);
+	return(append_delims(mdoc, tok, line, pos, buf));
 }
 
 
 /*
- * Implicity-scoped macros, like `.Ss', have a scope that terminates
- * with a subsequent call to the same macro.  Implicit macros cannot
- * break the scope of explicitly-scoped macros; however, they can break
- * the scope of other implicit macros (so `.Sh' can break `.Ss').  This
- * is ok with macros like `.It' because they exist only within an
- * explicit context.
- *
- * These macros put line arguments (which it's allowed to have) into the
- * HEAD section and open a BODY scope to be used until the macro scope
- * closes.
+ * Implicit- or explicit-end multi-line scoped macro.
  */
 int
-macro_scoped_implicit(MACRO_PROT_ARGS)
+macro_scoped(MACRO_PROT_ARGS)
 {
-	int		  lastarg, j;
-	char		 *args[MDOC_LINEARG_MAX];
-	struct mdoc_node *n;
-
-	assert( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags));
+	int		  c, lastarg, argc, j, fl;
+	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
+	char		 *p;
 
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
+	assert ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags));
 
-	/* Token pre-processing. */
+	if ( ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)) {
+		if ( ! rewind_subblock(MDOC_BODY, mdoc, tok))
+			return(0);
+		if ( ! rewind_impblock(mdoc, tok, line, ppos))
+			return(0);
+	}
 
-	switch (tok) {
-	case (MDOC_Ss):
-		/* FALLTHROUGH */
-	case (MDOC_Sh):
-		/* `.Pp' ignored when preceding `.Ss' or `.Sh'. */
-		if (NULL == mdoc->last)
-			break;
-		if (MDOC_ELEM != mdoc->last->type)
+	for (argc = 0; argc < MDOC_LINEARG_MAX; argc++) {
+		lastarg = *pos;
+		c = mdoc_argv(mdoc, line, tok, &argv[argc], pos, buf);
+		if (ARGV_EOLN == c)
 			break;
-		if (MDOC_Pp != mdoc->last->data.elem.tok)
+		if (ARGV_WORD == c) {
+			*pos = lastarg;
 			break;
-		if ( ! mdoc_warn(mdoc, tok, ppos, WARN_IGN_BEFORE_BLK))
-			return(0);
-		assert(mdoc->last->prev);
-		n = mdoc->last;
-		mdoc_msg(mdoc, ppos, "removing prior `Pp' macro");
-		mdoc->last = mdoc->last->prev;
-		mdoc->last->next = NULL;
-		mdoc_node_free(n);
-		break;
-	default:
-		break;
+		} else if (ARGV_ARG == c)
+			continue;
+		mdoc_argv_free(argc, argv);
+		return(0);
+	}
+
+	if (MDOC_LINEARG_MAX == argc) {
+		mdoc_argv_free(argc, argv);
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
 	}
 
-	/* Rewind our scope. */
+	c = mdoc_block_alloc(mdoc, line, ppos, 
+			tok, (size_t)argc, argv);
+	mdoc_argv_free(argc, argv);
 
-	if ( ! scope_rewind_imp(mdoc, ppos, tok))
+	if (0 == c)
 		return(0);
 
-	j = 0;
-	lastarg = ppos;
+	mdoc->next = MDOC_NEXT_CHILD;
 
-	/*
-	 * Process until we hit a line.  Note that current implicit
-	 * macros don't have any arguments, so we don't need to do any
-	 * argument processing.
-	 */
+	if (0 == buf[*pos]) {
+		if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+			return(0);
+		if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+			return(0);
+		if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+			return(0);
+		mdoc->next = MDOC_NEXT_CHILD;
+		return(1);
+	}
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
+	if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+		return(0);
+	mdoc->next = MDOC_NEXT_CHILD;
 
-	lastarg = *pos;
+	fl = ARGS_DELIM;
+	if (MDOC_TABSEP & mdoc_macros[tok].flags)
+		fl |= ARGS_TABSEP;
 
-	switch (mdoc_args(mdoc, tok, pos, buf, 0, &args[j])) {
-	case (ARGS_ERROR):
-		return(0);
-	case (ARGS_EOLN):
-		return(append_scoped(mdoc, tok, ppos, j, _CC(args), 0, NULL));
-	default:
+	for (j = 0; j < MDOC_LINEARG_MAX; j++) {
+		lastarg = *pos;
+		c = mdoc_args(mdoc, line, pos, buf, fl, &p);
+	
+		if (ARGS_ERROR == c)
+			return(0);
+		if (ARGS_PUNCT == c)
+			break;
+		if (ARGS_EOLN == c)
+			break;
+	
+		if (-1 == (c = lookup(mdoc, line, lastarg, tok, p)))
+			return(0);
+		else if (MDOC_MAX == c) {
+			if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
+				return(0);
+			mdoc->next = MDOC_NEXT_SIBLING;
+			continue;
+		} 
+
+		if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf))
+			return(0);
 		break;
 	}
 
-	if (MDOC_MAX != mdoc_find(mdoc, args[j]))
-		if ( ! mdoc_warn(mdoc, tok, lastarg, WARN_SYNTAX_MACLIKE))
-			return(0);
+	if (j == MDOC_LINEARG_MAX)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
 
-	j++;
-	goto again;
-	/* NOTREACHED */
+	if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+		return(0);
+	if (1 == ppos && ! append_delims(mdoc, tok, line, pos, buf))
+		return(0);
+
+	if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+		return(0);
+	mdoc->next = MDOC_NEXT_CHILD;
+
+	return(1);
 }
 
 
 /*
- * A line-scoped macro opens a scope for the contents of its line, which
- * are placed under the HEAD node.  Punctuation trailing the line is put
- * as a sibling to the HEAD node, under the BLOCK node.  
+ * When scoped to a line, a macro encompasses all of the contents.  This
+ * differs from constants or text macros, where a new macro will
+ * terminate the existing context.
  */
 int
 macro_scoped_line(MACRO_PROT_ARGS)
 {
 	int		  lastarg, c, j;
 	char		  *p;
-	struct mdoc_node  *n;
-
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
-
-	assert(1 == ppos);
-	
-	/* Token pre-processing.  */
-
-	switch (tok) {
-	case (MDOC_D1):
-		/* FALLTHROUGH */
-	case (MDOC_Dl):
-		/* These can't be nested in a display block. */
-		assert(mdoc->last);
-		for (n = mdoc->last->parent ; n; n = n->parent)
-			if (MDOC_BLOCK != n->type) 
-				continue;
-			else if (MDOC_Bd == n->data.block.tok)
-				break;
-		if (NULL == n)
-			break;
-		return(mdoc_err(mdoc, tok, ppos, ERR_SCOPE_NONEST));
-	default:
-		break;
-	}
-
-	/*
-	 * All line-scoped macros have a HEAD and optionally a BODY
-	 * section.  We open our scope here; when we exit this function,
-	 * we'll rewind our scope appropriately.
-	 */
-
-	mdoc_block_alloc(mdoc, ppos, tok, 0, NULL);
-	mdoc_head_alloc(mdoc, ppos, tok, 0, NULL);
 
-	/* Process line parameters. */
+	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, 0, NULL))
+		return(0);
+	mdoc->next = MDOC_NEXT_CHILD;
 
-	j = 0;
-	lastarg = ppos;
+	if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+		return(0);
+	mdoc->next = MDOC_NEXT_CHILD;
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
+	/* XXX - no known argument macros. */
 
-	lastarg = *pos;
-	c = mdoc_args(mdoc, tok, pos, buf, ARGS_DELIM, &p);
+	for (lastarg = ppos, j = 0; j < MDOC_LINEARG_MAX; j++) {
+		lastarg = *pos;
+		c = mdoc_args(mdoc, line, pos, buf, ARGS_DELIM, &p);
 
-	switch (c) {
-	case (ARGS_ERROR):
-		return(0);
-	case (ARGS_WORD):
-		break;
-	case (ARGS_PUNCT):
-		if ( ! append_delims(mdoc, tok, pos, buf))
+		if (ARGS_ERROR == c)
 			return(0);
-		return(scope_rewind_imp(mdoc, ppos, tok));
-	case (ARGS_EOLN):
-		return(scope_rewind_imp(mdoc, ppos, tok));
-	default:
-		abort();
-		/* NOTREACHED */
-	}
+		if (ARGS_PUNCT == c)
+			break;
+		if (ARGS_EOLN == c)
+			break;
 
-	if (MDOC_MAX != (c = mdoc_find(mdoc, p))) {
-		if ( ! mdoc_macro(mdoc, c, lastarg, pos, buf))
+		if (-1 == (c = lookup(mdoc, line, lastarg, tok, p)))
 			return(0);
-		if ( ! append_delims(mdoc, tok, pos, buf))
+		else if (MDOC_MAX == c) {
+			if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
+				return(0);
+			mdoc->next = MDOC_NEXT_SIBLING;
+			continue;
+		} 
+
+		if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf))
 			return(0);
-		return(scope_rewind_imp(mdoc, ppos, tok));
+		break;
 	}
 
-	if (mdoc_isdelim(p))
-		j = 0;
+	if (j == MDOC_LINEARG_MAX)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
 
-	mdoc_word_alloc(mdoc, lastarg, p);
-	goto again;
-	/* NOTREACHED */
+	if (1 == ppos) {
+		if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+			return(0);
+		if ( ! append_delims(mdoc, tok, line, pos, buf))
+			return(0);
+	} else if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+		return(0);
+	return(rewind_impblock(mdoc, tok, line, ppos));
 }
 
 
-/* 
- * Partial-line scope is identical to line scope (macro_scoped_line())
- * except that trailing punctuation is appended to the BLOCK, instead of
- * contained within the HEAD.
+/*
+ * Constant-scope macros accept a fixed number of arguments and behave
+ * like constant macros except that they're scoped across lines.
  */
 int
-macro_scoped_pline(MACRO_PROT_ARGS)
+macro_constant_scoped(MACRO_PROT_ARGS)
 {
-	int		  lastarg, c, j;
-	char		  *p;
-
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
+	int		  lastarg, flushed, j, c, maxargs;
+	char		 *p;
 
-	/* Token pre-processing.  */
+	lastarg = ppos;
+	flushed = 0;
 
 	switch (tok) {
-	case (MDOC_Ql):
-		if ( ! mdoc_warn(mdoc, tok, ppos, WARN_COMPAT_TROFF))
-			return(0);
+	case (MDOC_Eo):
+		maxargs = 1;
 		break;
 	default:
+		maxargs = 0;
 		break;
 	}
 
-	mdoc_block_alloc(mdoc, ppos, tok, 0, NULL);
-	mdoc_head_alloc(mdoc, ppos, tok, 0, NULL);
+	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, 0, NULL))
+		return(0); 
+	mdoc->next = MDOC_NEXT_CHILD;
+
+	if (0 == maxargs) {
+		if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+			return(0);
+		if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+			return(0);
+		if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+			return(0);
+		flushed = 1;
+	} else if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
+		return(0);
+
+	mdoc->next = MDOC_NEXT_CHILD;
 
-	/* Process line parameters. */
+	for (j = 0; j < MDOC_LINEARG_MAX; j++) {
+		lastarg = *pos;
 
-	j = 0;
-	lastarg = ppos;
+		if (j == maxargs && ! flushed) {
+			if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+				return(0);
+			flushed = 1;
+			if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+				return(0);
+			mdoc->next = MDOC_NEXT_CHILD;
+		}
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
+		c = mdoc_args(mdoc, line, pos, buf, ARGS_DELIM, &p);
+		if (ARGS_ERROR == c)
+			return(0);
+		if (ARGS_PUNCT == c)
+			break;
+		if (ARGS_EOLN == c)
+			break;
 
-	lastarg = *pos;
-	c = mdoc_args(mdoc, tok, pos, buf, ARGS_DELIM, &p);
+		if (-1 == (c = lookup(mdoc, line, lastarg, tok, p)))
+			return(0);
+		else if (MDOC_MAX != c) {
+			if ( ! flushed) {
+				if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+					return(0);
+				flushed = 1;
+				if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+					return(0);
+				mdoc->next = MDOC_NEXT_CHILD;
+			}
+			if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf))
+				return(0);
+			break;
+		}
 
-	switch (c) {
-	case (ARGS_ERROR):
-		return(0);
-	case (ARGS_WORD):
-		break;
-	case (ARGS_PUNCT):
-		if ( ! scope_rewind_imp(mdoc, ppos, tok))
+		if ( ! flushed && mdoc_isdelim(p)) {
+			if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
+				return(0);
+			flushed = 1;
+			if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+				return(0);
+			mdoc->next = MDOC_NEXT_CHILD;
+		}
+	
+		if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
 			return(0);
-		if (ppos > 1)
-			return(1);
-		return(append_delims(mdoc, tok, pos, buf));
-	case (ARGS_EOLN):
-		return(scope_rewind_imp(mdoc, ppos, tok));
-	default:
-		abort();
-		/* NOTREACHED */
+		mdoc->next = MDOC_NEXT_SIBLING;
 	}
 
-	if (MDOC_MAX != (c = mdoc_find(mdoc, p))) {
-		if ( ! mdoc_macro(mdoc, c, lastarg, pos, buf))
+	if (MDOC_LINEARG_MAX == j)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
+
+	if ( ! flushed) {
+		if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok))
 			return(0);
-		if ( ! scope_rewind_imp(mdoc, ppos, tok))
+		if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
 			return(0);
-		if (ppos > 1)
-			return(1);
-		return(append_delims(mdoc, tok, pos, buf));
+		mdoc->next = MDOC_NEXT_CHILD;
 	}
 
-	if (mdoc_isdelim(p))
-		j = 0;
-
-	mdoc_word_alloc(mdoc, lastarg, p);
-	goto again;
-	/* NOTREACHED */
+	if (ppos > 1)
+		return(1);
+	return(append_delims(mdoc, tok, line, pos, buf));
 }
 
 
 /*
- * A delimited-constant macro is similar to a general text macro: the
- * macro is followed by a 0 or 1 arguments (possibly-unspecified) then
- * terminating punctuation, other words, or another callable macro.
+ * Delimited macros are like text macros except that, should punctuation
+ * be encountered, the macro isn't re-started with remaining tokens
+ * (it's only emitted once).  Delimited macros can have a maximum number
+ * of arguments.
  */
 int
 macro_constant_delimited(MACRO_PROT_ARGS)
 {
-	int		  lastarg, flushed, c, maxargs;
+	int		  lastarg, flushed, j, c, maxargs, argc;
+	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
 	char		 *p;
 
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
-
-	/* Process line parameters. */
-
 	lastarg = ppos;
 	flushed = 0;
 
-	/* Token pre-processing. */
-
 	switch (tok) {
+	case (MDOC_No):
+		/* FALLTHROUGH */
+	case (MDOC_Ns):
+		/* FALLTHROUGH */
+	case (MDOC_Pf):
+		/* FALLTHROUGH */
 	case (MDOC_Ux):
+		/* FALLTHROUGH */
+	case (MDOC_St):
 		maxargs = 0;
 		break;
 	default:
@@ -870,138 +962,164 @@ macro_constant_delimited(MACRO_PROT_ARGS)
 		break;
 	}
 
-again:
-	lastarg = *pos;
-
-	switch (mdoc_args(mdoc, tok, pos, buf, ARGS_DELIM, &p)) {
-	case (ARGS_ERROR):
+	for (argc = 0; argc < MDOC_LINEARG_MAX; argc++) {
+		lastarg = *pos;
+		c = mdoc_argv(mdoc, line, tok, &argv[argc], pos, buf);
+		if (ARGV_EOLN == c)
+			break;
+		if (ARGV_WORD == c) {
+			*pos = lastarg;
+			break;
+		} else if (ARGV_ARG == c)
+			continue;
+		mdoc_argv_free(argc, argv);
 		return(0);
-	case (ARGS_WORD):
-		break;
-	case (ARGS_PUNCT):
-		if ( ! flushed && ! append_const(mdoc, tok, ppos, 0, &p))
-			return(0);
-		if (ppos > 1)
-			return(1);
-		return(append_delims(mdoc, tok, pos, buf));
-	case (ARGS_EOLN):
-		if (flushed)
-			return(1);
-		return(append_const(mdoc, tok, ppos, 0, &p));
-	default:
-		abort();
-		/* NOTREACHED */
 	}
 
-	/* Accepts no arguments: flush out symbol and continue. */
+	c = mdoc_elem_alloc(mdoc, line, ppos, tok, argc, argv);
+	mdoc_argv_free(argc, argv);
 
-	if (0 == maxargs) {
-		if ( ! append_const(mdoc, tok, ppos, 0, &p))
+	if (0 == c)
+		return(0);
+
+	mdoc->next = MDOC_NEXT_CHILD;
+
+	for (j = 0; j < MDOC_LINEARG_MAX; j++) {
+		lastarg = *pos;
+
+		if (j == maxargs && ! flushed) {
+			if ( ! rewind_elem(mdoc, tok))
+				return(0);
+			flushed = 1;
+		}
+
+		c = mdoc_args(mdoc, line, pos, buf, ARGS_DELIM, &p);
+		if (ARGS_ERROR == c)
 			return(0);
-		flushed = 1;
-	}
+		if (ARGS_PUNCT == c)
+			break;
+		if (ARGS_EOLN == c)
+			break;
 
-	if (MDOC_MAX != (c = mdoc_find(mdoc, p))) {
-		if ( ! flushed && ! append_const(mdoc, tok, ppos, 0, &p))
+		if (-1 == (c = lookup(mdoc, line, lastarg, tok, p)))
 			return(0);
-		if ( ! mdoc_macro(mdoc, c, lastarg, pos, buf))
+		else if (MDOC_MAX != c) {
+			if ( ! flushed && ! rewind_elem(mdoc, tok))
+				return(0);
+			flushed = 1;
+			if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf))
+				return(0);
+			break;
+		}
+
+		if ( ! flushed && mdoc_isdelim(p)) {
+			if ( ! rewind_elem(mdoc, tok))
+				return(0);
+			flushed = 1;
+		}
+	
+		if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
 			return(0);
-		if (ppos > 1)
-			return(1);
-		return(append_delims(mdoc, tok, pos, buf));
+		mdoc->next = MDOC_NEXT_SIBLING;
 	}
 
-	/* 
-	 * We only accept one argument; subsequent tokens are considered
-	 * as literal words (until a macro).
-	 */
+	if (MDOC_LINEARG_MAX == j)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
 
-	if ( ! flushed && ! mdoc_isdelim(p)) {
-	       if ( ! append_const(mdoc, tok, ppos, 1, &p))
-			return(0);
-		flushed = 1;
-		goto again;
-	} else if ( ! flushed) {
-		if ( ! append_const(mdoc, tok, ppos, 0, &p))
-			return(0);
-		flushed = 1;
-	}
+	if ( ! flushed && rewind_elem(mdoc, tok))
+		return(0);
 
-	mdoc_word_alloc(mdoc, lastarg, p);
-	goto again;
-	/* NOTREACHED */
+	if (ppos > 1)
+		return(1);
+	return(append_delims(mdoc, tok, line, pos, buf));
 }
 
 
+/*
+ * Constant macros span an entire line:  they constitute a macro and all
+ * of its arguments and child data.
+ */
 int
 macro_constant(MACRO_PROT_ARGS)
 {
-	int		  lastarg, j;
-	char		 *args[MDOC_LINEARG_MAX];
+	int		 c, lastarg, argc, sz, fl;
+	struct mdoc_arg	 argv[MDOC_LINEARG_MAX];
+	char		*p;
 
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
+	fl = 0;
+	if (MDOC_QUOTABLE & mdoc_macros[tok].flags)
+		fl = ARGS_QUOTED;
 
-	j = 0;
-	lastarg = ppos;
+	for (argc = 0; argc < MDOC_LINEARG_MAX; argc++) {
+		lastarg = *pos;
+		c = mdoc_argv(mdoc, line, tok, &argv[argc], pos, buf);
+		if (ARGV_EOLN == c) 
+			break;
+		if (ARGV_WORD == c) {
+			*pos = lastarg;
+			break;
+		} else if (ARGV_ARG == c)
+			continue;
 
-again:
-	if (j == MDOC_LINEARG_MAX)
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
+		mdoc_argv_free(argc, argv);
+		return(0);
+	}
 
-	lastarg = *pos;
+	c = mdoc_elem_alloc(mdoc, line, ppos, tok, argc, argv);
+	mdoc_argv_free(argc, argv);
 
-	switch (mdoc_args(mdoc, tok, pos, buf, 0, &args[j])) {
-	case (ARGS_ERROR):
+	if (0 == c)
 		return(0);
-	case (ARGS_WORD):
-		break;
-	case (ARGS_EOLN):
-		return(append_const(mdoc, tok, ppos, j, args));
-	default:
-		abort();
-		/* NOTREACHED */
-	}
 
-	if (MDOC_MAX != mdoc_find(mdoc, args[j])) 
-		if ( ! mdoc_warn(mdoc, tok, lastarg, WARN_SYNTAX_MACLIKE))
+	mdoc->next = MDOC_NEXT_CHILD;
+
+	if (MDOC_LINEARG_MAX == argc)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
+
+	for (sz = 0; sz + argc < MDOC_LINEARG_MAX; sz++) {
+		lastarg = *pos;
+		c = mdoc_args(mdoc, line, pos, buf, fl, &p);
+		if (ARGS_ERROR == c)
+			return(0);
+		if (ARGS_EOLN == c)
+			break;
+
+		if (-1 == (c = lookup(mdoc, line, lastarg, tok, p)))
 			return(0);
+		else if (MDOC_MAX != c) {
+			if ( ! rewind_elem(mdoc, tok))
+				return(0);
+			return(mdoc_macro(mdoc, c, line, 
+						lastarg, pos, buf));
+		}
 
-	j++;
-	goto again;
-	/* NOTREACHED */
+		if ( ! mdoc_word_alloc(mdoc, line, lastarg, p))
+			return(0);
+		mdoc->next = MDOC_NEXT_SIBLING;
+	}
+
+	if (MDOC_LINEARG_MAX == sz + argc)
+		return(mdoc_perr(mdoc, line, ppos, "too many arguments"));
+
+	return(rewind_elem(mdoc, tok));
 }
 
 
+/* ARGSUSED */
 int
-macro_constant_argv(MACRO_PROT_ARGS)
+macro_obsolete(MACRO_PROT_ARGS)
 {
-	int		  c, lastarg, j;
-	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
-
-	if (SEC_PROLOGUE == mdoc->sec_lastn)
-		return(mdoc_err(mdoc, tok, ppos, ERR_SEC_PROLOGUE));
 
-	lastarg = *pos;
-
-	for (j = 0; j < MDOC_LINEARG_MAX; j++) {
-		lastarg = *pos;
-		c = mdoc_argv(mdoc, tok, &argv[j], pos, buf);
-		if (0 == c)
-			break;
-		else if (1 == c)
-			continue;
+	return(mdoc_pwarn(mdoc, line, ppos, WARN_SYNTAX, "macro is obsolete"));
+}
 
-		mdoc_argv_free(j, argv);
-		return(0);
-	}
 
-	if (MDOC_LINEARG_MAX == j) {
-		mdoc_argv_free(j, argv);
-		return(mdoc_err(mdoc, tok, lastarg, ERR_ARGS_MANY));
-	}
+int
+macro_end(struct mdoc *mdoc)
+{
 
-	c = append_constarg(mdoc, tok, ppos, j, argv);
-	mdoc_argv_free(j, argv);
-	return(c);
+	assert(mdoc->first);
+	assert(mdoc->last);
+	return(rewind_last(mdoc->last->tok, mdoc->last->type,
+				mdoc, mdoc->first));
 }