X-Git-Url: https://git.cameronkatri.com/mandoc.git/blobdiff_plain/c4da36392950a99306bb7aae41a441fdf8e9f48f..9d70ad9e783183f60b7bf9ded3496003a1298237:/macro.c

diff --git a/macro.c b/macro.c
index c18cfe63..ea4af29b 100644
--- a/macro.c
+++ b/macro.c
@@ -1,4 +1,4 @@
-/* $Id: macro.c,v 1.48 2009/01/20 20:56:21 kristaps Exp $ */
+/* $Id: macro.c,v 1.58 2009/03/08 11:41:22 kristaps Exp $ */
 /*
  * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
  *
@@ -28,58 +28,24 @@
 /*
  * This has scanning/parsing routines, each of which extract a macro and
  * its arguments and parameters, then know how to progress to the next
- * macro.  Macros are parsed according as follows:
- *
- *   ELEMENT:	  TEXT | epsilon
- *   BLOCK:	  HEAD PUNCT BODY PUNCT BLOCK_TAIL PUNCT
- *   BLOCK_TAIL:  TAIL | epsilon
- *   HEAD:	  ELEMENT | TEXT | BLOCK | epsilon
- *   BODY:	  ELEMENT | TEXT | BLOCK | epsilon
- *   TAIL:	  TEXT | epsilon
- *   PUNCT:	  TEXT (delimiters) | epsilon
- *
- * These are arranged into a parse tree, an example of which follows:
- *
- *   ROOT
- *       BLOCK (.Sh)
- *           HEAD
- *               TEXT (`NAME')
- *           BODY
- *               ELEMENT (.Nm)
- *                   TEXT (`mdocml')
- *               ELEMENT (.Nd)
- *                   TEXT (`mdoc macro compiler')
- *               BLOCK (.Op)
- *                   HEAD
- *                       ELEMENT (.Fl)
- *                           TEXT (`v')
- *               BLOCK (.Op)
- *                   HEAD
- *                       ELEMENT (.Fl)
- *                           TEXT (`v')
- *                       ELEMENT (.Fl)
- *                           TEXT (`W')
- *                       ELEMENT (.Ns)
- *                       ELEMENT (.Ar)
- *                           TEXT (`err...')
- *
- * These types are always per-line except for block bodies, which may
- * span multiple lines.  Macros are assigned a parsing routine, which
- * corresponds to the type, in the mdoc_macros table.
- *
- * Note that types are general:  there can be several parsing routines
- * corresponding to a single type.  The macro_text function, for
- * example, parses an ELEMENT type (see the function definition for
- * details) that may be interrupted by further macros; the
- * macro_constant function, on the other hand, parses an ELEMENT type
- * spanning a single line.
+ * macro. 
  */
 
 #include "private.h"
 
+static int	  macro_obsolete(MACRO_PROT_ARGS);
+static int	  macro_constant(MACRO_PROT_ARGS);
+static int	  macro_constant_scoped(MACRO_PROT_ARGS);
+static int	  macro_constant_delimited(MACRO_PROT_ARGS);
+static int	  macro_text(MACRO_PROT_ARGS);
+static int	  macro_scoped(MACRO_PROT_ARGS);
+static int	  macro_scoped_close(MACRO_PROT_ARGS);
+static int	  macro_scoped_line(MACRO_PROT_ARGS);
+
 #define	REWIND_REWIND	(1 << 0)
 #define	REWIND_NOHALT	(1 << 1)
 #define	REWIND_HALT	(1 << 2)
+
 static	int	  rewind_dohalt(int, enum mdoc_type, 
 			const struct mdoc_node *);
 static	int	  rewind_alt(int);
@@ -92,6 +58,173 @@ static	int	  rewind_subblock(enum mdoc_type,
 static	int	  rewind_last(struct mdoc *, struct mdoc_node *);
 static	int	  append_delims(struct mdoc *, int, int *, char *);
 static	int	  lookup(struct mdoc *, int, int, int, const char *);
+static	int	  pwarn(struct mdoc *, int, int, int);
+static	int	  perr(struct mdoc *, int, int, int);
+
+#define	WMACPARM	(1)
+#define	WOBS		(2)
+
+#define	ENOCTX		(1)
+#define	ENOPARMS	(2)
+
+/* Central table of library: who gets parsed how. */
+
+const	struct mdoc_macro __mdoc_macros[MDOC_MAX] = {
+	{ NULL, 0 }, /* \" */
+	{ macro_constant, MDOC_PROLOGUE }, /* Dd */
+	{ macro_constant, MDOC_PROLOGUE }, /* Dt */
+	{ macro_constant, MDOC_PROLOGUE }, /* Os */
+	{ macro_scoped, 0 }, /* Sh */
+	{ macro_scoped, 0 }, /* Ss */ 
+	{ macro_text, 0 }, /* Pp */ 
+	{ macro_scoped_line, MDOC_PARSED }, /* D1 */
+	{ macro_scoped_line, MDOC_PARSED }, /* Dl */
+	{ macro_scoped, MDOC_EXPLICIT }, /* Bd */
+	{ macro_scoped_close, MDOC_EXPLICIT }, /* Ed */
+	{ macro_scoped, MDOC_EXPLICIT }, /* Bl */
+	{ macro_scoped_close, MDOC_EXPLICIT }, /* El */
+	{ macro_scoped, MDOC_PARSED }, /* It */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Ad */ 
+	{ macro_text, MDOC_PARSED }, /* An */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Ar */
+	{ macro_constant, 0 }, /* Cd */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Cm */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Dv */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Er */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Ev */ 
+	{ macro_constant, 0 }, /* Ex */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Fa */ 
+	{ macro_constant, 0 }, /* Fd */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Fl */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Fn */ 
+	{ macro_text, MDOC_PARSED }, /* Ft */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Ic */ 
+	{ macro_constant, 0 }, /* In */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Li */
+	{ macro_constant, 0 }, /* Nd */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Nm */ 
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Op */
+	{ macro_obsolete, 0 }, /* Ot */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Pa */
+	{ macro_constant, 0 }, /* Rv */
+	/* XXX - .St supposed to be (but isn't) callable. */
+	{ macro_constant_delimited, MDOC_PARSED }, /* St */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Va */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Vt */ 
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Xr */
+	{ macro_constant, 0 }, /* %A */
+	{ macro_constant, 0 }, /* %B */
+	{ macro_constant, 0 }, /* %D */
+	{ macro_constant, 0 }, /* %I */
+	{ macro_constant, 0 }, /* %J */
+	{ macro_constant, 0 }, /* %N */
+	{ macro_constant, 0 }, /* %O */
+	{ macro_constant, 0 }, /* %P */
+	{ macro_constant, 0 }, /* %R */
+	{ macro_constant, 0 }, /* %T */
+	{ macro_constant, 0 }, /* %V */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ac */
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Ao */
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Aq */
+	{ macro_constant_delimited, 0 }, /* At */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Bc */
+	{ macro_scoped, MDOC_EXPLICIT }, /* Bf */ 
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Bo */
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Bq */
+	{ macro_constant_delimited, MDOC_PARSED }, /* Bsx */
+	{ macro_constant_delimited, MDOC_PARSED }, /* Bx */
+	{ macro_constant, 0 }, /* Db */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Dc */
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Do */
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Dq */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Ec */
+	{ macro_scoped_close, MDOC_EXPLICIT }, /* Ef */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Em */ 
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Eo */
+	{ macro_constant_delimited, MDOC_PARSED }, /* Fx */
+	{ macro_text, MDOC_PARSED }, /* Ms */
+	{ macro_constant_delimited, MDOC_CALLABLE | MDOC_PARSED }, /* No */
+	{ macro_constant_delimited, MDOC_CALLABLE | MDOC_PARSED }, /* Ns */
+	{ macro_constant_delimited, MDOC_PARSED }, /* Nx */
+	{ macro_constant_delimited, MDOC_PARSED }, /* Ox */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Pc */
+	{ macro_constant_delimited, MDOC_PARSED }, /* Pf */
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Po */
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Pq */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Qc */
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Ql */
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Qo */
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Qq */
+	{ macro_scoped_close, MDOC_EXPLICIT }, /* Re */
+	{ macro_scoped, MDOC_EXPLICIT }, /* Rs */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Sc */
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* So */
+	{ macro_scoped_line, MDOC_CALLABLE | MDOC_PARSED }, /* Sq */
+	{ macro_constant, 0 }, /* Sm */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Sx */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Sy */
+	{ macro_text, MDOC_CALLABLE | MDOC_PARSED }, /* Tn */
+	{ macro_constant_delimited, MDOC_PARSED }, /* Ux */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Xc */
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Xo */
+	/* XXX - .Fo supposed to be (but isn't) callable. */
+	{ macro_scoped, MDOC_EXPLICIT }, /* Fo */ 
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Fc */ 
+	{ macro_constant_scoped, MDOC_CALLABLE | MDOC_PARSED | MDOC_EXPLICIT }, /* Oo */
+	{ macro_scoped_close, MDOC_EXPLICIT | MDOC_CALLABLE | MDOC_PARSED }, /* Oc */
+	{ macro_scoped, MDOC_EXPLICIT }, /* Bk */
+	{ macro_scoped_close, MDOC_EXPLICIT }, /* Ek */
+	{ macro_constant, 0 }, /* Bt */
+	{ macro_constant, 0 }, /* Hf */
+	{ macro_obsolete, 0 }, /* Fr */
+	{ macro_constant, 0 }, /* Ud */
+	{ macro_constant, 0 }, /* Lb */
+};
+
+const	struct mdoc_macro * const mdoc_macros = __mdoc_macros;
+
+
+static int
+perr(struct mdoc *mdoc, int line, int pos, int type)
+{
+	int		 c;
+
+	switch (type) {
+	case (ENOCTX):
+		c = mdoc_perr(mdoc, line, pos, 
+				"closing macro has prior context");
+		break;
+	case (ENOPARMS):
+		c = mdoc_perr(mdoc, line, pos, 
+				"macro doesn't expect parameters");
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+	return(c);
+}
+
+static int
+pwarn(struct mdoc *mdoc, int line, int pos, int type)
+{
+	int		 c;
+
+	switch (type) {
+	case (WMACPARM):
+		c = mdoc_pwarn(mdoc, line, pos, WARN_SYNTAX,
+				"macro-like parameter");
+		break;
+	case (WOBS):
+		c = mdoc_pwarn(mdoc, line, pos, WARN_SYNTAX,
+				"macro is marked obsolete");
+		break;
+	default:
+		abort();
+		/* NOTREACHED */
+	}
+	return(c);
+}
 
 
 static int
@@ -99,12 +232,12 @@ lookup(struct mdoc *mdoc, int line, int pos, int from, const char *p)
 {
 	int		 res;
 
-	res = mdoc_find(mdoc, p);
+	res = mdoc_tokhash_find(mdoc->htab, p);
 	if (MDOC_PARSED & mdoc_macros[from].flags)
 		return(res);
 	if (MDOC_MAX == res)
 		return(res);
-	if ( ! mdoc_pwarn(mdoc, line, pos, WARN_SYNTAX, "macro-like parameter"))
+	if ( ! pwarn(mdoc, line, pos, WMACPARM))
 		return(-1);
 	return(MDOC_MAX);
 }
@@ -117,6 +250,7 @@ rewind_last(struct mdoc *mdoc, struct mdoc_node *to)
 	assert(to);
 	mdoc->next = MDOC_NEXT_SIBLING;
 
+	/* LINTED */
 	while (mdoc->last != to) {
 		if ( ! mdoc_valid_post(mdoc))
 			return(0);
@@ -378,7 +512,9 @@ rewind_subblock(enum mdoc_type type, struct mdoc *mdoc,
 			break;
 		else if (rewind_dobreak(tok, n))
 			continue;
-		return(mdoc_perr(mdoc, line, ppos, "scope breaks prior %s", mdoc_node2a(n)));
+		return(mdoc_perr(mdoc, line, ppos, 
+			"scope breaks %s", MDOC_ROOT == n->type ?
+			"<root>" : mdoc_macronames[n->tok]));
 	}
 
 	assert(n);
@@ -396,12 +532,14 @@ rewind_expblock(struct mdoc *mdoc, int tok, int line, int ppos)
 	for (n = mdoc->last; 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"));
+			return(perr(mdoc, line, ppos, ENOCTX));
 		if (REWIND_REWIND == c)
 			break;
 		else if (rewind_dobreak(tok, n))
 			continue;
-		return(mdoc_perr(mdoc, line, ppos, "scope breaks prior %s", mdoc_node2a(n)));
+		return(mdoc_perr(mdoc, line, ppos, 
+			"scope breaks %s", MDOC_ROOT == n->type ?
+			"<root>" : mdoc_macronames[n->tok]));
 	}
 
 	assert(n);
@@ -424,7 +562,9 @@ rewind_impblock(struct mdoc *mdoc, int tok, int line, int ppos)
 			break;
 		else if (rewind_dobreak(tok, n))
 			continue;
-		return(mdoc_perr(mdoc, line, ppos, "scope breaks prior %s", mdoc_node2a(n)));
+		return(mdoc_perr(mdoc, line, ppos, 
+			"scope breaks %s", MDOC_ROOT == n->type ?
+			"<root>" : mdoc_macronames[n->tok]));
 	}
 
 	assert(n);
@@ -444,6 +584,8 @@ append_delims(struct mdoc *mdoc, int line, int *pos, char *buf)
 	for (;;) {
 		lastarg = *pos;
 		c = mdoc_args(mdoc, line, pos, buf, 0, &p);
+		assert(ARGS_PHRASE != c);
+
 		if (ARGS_ERROR == c)
 			return(0);
 		else if (ARGS_EOLN == c)
@@ -462,7 +604,7 @@ append_delims(struct mdoc *mdoc, int line, int *pos, char *buf)
  * Close out an explicit scope.  This optionally parses a TAIL type with
  * a set number of TEXT children.
  */
-int
+static int
 macro_scoped_close(MACRO_PROT_ARGS)
 {
 	int	 	 tt, j, c, lastarg, maxargs, flushed;
@@ -484,11 +626,12 @@ macro_scoped_close(MACRO_PROT_ARGS)
 
 	if ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) {
 		if (0 == buf[*pos]) {
-			if ( ! rewind_subblock(MDOC_BODY, mdoc, tok, line, ppos))
+			if ( ! rewind_subblock(MDOC_BODY, mdoc, 
+						tok, line, ppos))
 				return(0);
 			return(rewind_expblock(mdoc, tok, line, ppos));
 		}
-		return(mdoc_perr(mdoc, line, ppos, "macro expects no parameters"));
+		return(perr(mdoc, line, ppos, ENOPARMS));
 	}
 
 	if ( ! rewind_subblock(MDOC_BODY, mdoc, tok, line, ppos))
@@ -513,6 +656,8 @@ macro_scoped_close(MACRO_PROT_ARGS)
 		}
 
 		c = mdoc_args(mdoc, line, pos, buf, tok, &p);
+		assert(ARGS_PHRASE != c);
+
 		if (ARGS_ERROR == c)
 			return(0);
 		if (ARGS_PUNCT == c)
@@ -524,7 +669,8 @@ macro_scoped_close(MACRO_PROT_ARGS)
 			return(0);
 		else if (MDOC_MAX != c) {
 			if ( ! flushed) {
-				if ( ! rewind_expblock(mdoc, tok, line, ppos))
+				if ( ! rewind_expblock(mdoc, tok, 
+							line, ppos))
 					return(0);
 				flushed = 1;
 			}
@@ -571,19 +717,20 @@ macro_scoped_close(MACRO_PROT_ARGS)
  *    TEXT (`;')
  *    TEXT (`;')
  */
-int
+static int
 macro_text(MACRO_PROT_ARGS)
 {
-	int		  la, lastpunct, c, w, argc;
-	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
+	int		  la, lastpunct, c, w;
+	struct mdoc_arg	 *arg;
 	char		 *p;
 
 	la = ppos;
 	lastpunct = 0;
+	arg = NULL;
 
-	for (argc = 0; argc < MDOC_LINEARG_MAX; argc++) {
+	for (;;) {
 		la = *pos;
-		c = mdoc_argv(mdoc, line, tok, &argv[argc], pos, buf);
+		c = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
 		if (ARGV_EOLN == c)
 			break;
 		if (ARGV_WORD == c) {
@@ -591,22 +738,12 @@ macro_text(MACRO_PROT_ARGS)
 			break;
 		} else if (ARGV_ARG == c)
 			continue;
-
-		mdoc_argv_free(argc, argv);
+		mdoc_argv_free(arg);
 		return(0);
 	}
 
-	if (MDOC_LINEARG_MAX == argc) {
-		mdoc_argv_free(argc - 1, argv);
-		return(mdoc_perr(mdoc, line, ppos, "parameter hard-limit exceeded"));
-	}
-
-	c = mdoc_elem_alloc(mdoc, line, ppos, tok, argc, argv);
-
-	if (0 == c) {
-		mdoc_argv_free(argc, argv);
+	if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
 		return(0);
-	}
 
 	mdoc->next = MDOC_NEXT_CHILD;
 
@@ -614,11 +751,10 @@ macro_text(MACRO_PROT_ARGS)
 	for (;;) {
 		la = *pos;
 		w = mdoc_args(mdoc, line, pos, buf, tok, &p);
-		if (ARGS_ERROR == w) {
-			mdoc_argv_free(argc, argv);
-			return(0);
-		}
+		assert(ARGS_PHRASE != c);
 
+		if (ARGS_ERROR == w)
+			return(0);
 		if (ARGS_EOLN == w)
 			break;
 		if (ARGS_PUNCT == w)
@@ -628,35 +764,29 @@ macro_text(MACRO_PROT_ARGS)
 			lookup(mdoc, line, la, tok, p);
 
 		if (MDOC_MAX != c && -1 != c) {
-			if (0 == lastpunct && ! rewind_elem(mdoc, tok)) {
-				mdoc_argv_free(argc, argv);
+			if (0 == lastpunct && ! rewind_elem(mdoc, tok))
 				return(0);
-			}
-			mdoc_argv_free(argc, argv);
 			c = mdoc_macro(mdoc, c, line, la, pos, buf);
 			if (0 == c)
 				return(0);
 			if (ppos > 1)
 				return(1);
 			return(append_delims(mdoc, line, pos, buf));
-		} else if (-1 == c) {
-			mdoc_argv_free(argc, argv);
+		} else if (-1 == c)
 			return(0);
-		}
+
+		/* FIXME: .Fl and .Ar handling of `|'. */
 
 		if (ARGS_QWORD != w && mdoc_isdelim(p)) {
-			if (0 == lastpunct && ! rewind_elem(mdoc, tok)) {
-				mdoc_argv_free(argc, argv);
+			if (0 == lastpunct && ! rewind_elem(mdoc, tok))
 				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);
+			c = mdoc_elem_alloc(mdoc, line, ppos, tok, arg);
+
+			if (0 == c)
 				return(0);
-			}
+
 			mdoc->next = MDOC_NEXT_CHILD;
 			lastpunct = 0;
 		}
@@ -666,8 +796,6 @@ macro_text(MACRO_PROT_ARGS)
 		mdoc->next = MDOC_NEXT_SIBLING;
 	}
 
-	mdoc_argv_free(argc, argv);
-
 	if (0 == lastpunct && ! rewind_elem(mdoc, tok))
 		return(0);
 	if (ppos > 1)
@@ -700,11 +828,11 @@ macro_text(MACRO_PROT_ARGS)
  * Note that the `.It' macro, possibly the most difficult (as it has
  * embedded scope, etc.) is handled by this routine.
  */
-int
+static int
 macro_scoped(MACRO_PROT_ARGS)
 {
-	int		  c, lastarg, argc;
-	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
+	int		  c, lastarg;
+	struct mdoc_arg	 *arg;
 	char		 *p;
 
 	assert ( ! (MDOC_CALLABLE & mdoc_macros[tok].flags));
@@ -719,10 +847,12 @@ macro_scoped(MACRO_PROT_ARGS)
 	}
 
 	/* Parse arguments. */
+	
+	arg = NULL;
 
-	for (argc = 0; argc < MDOC_LINEARG_MAX; argc++) {
+	for (;;) {
 		lastarg = *pos;
-		c = mdoc_argv(mdoc, line, tok, &argv[argc], pos, buf);
+		c = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
 		if (ARGV_EOLN == c)
 			break;
 		if (ARGV_WORD == c) {
@@ -730,20 +860,11 @@ macro_scoped(MACRO_PROT_ARGS)
 			break;
 		} else if (ARGV_ARG == c)
 			continue;
-		mdoc_argv_free(argc, argv);
+		mdoc_argv_free(arg);
 		return(0);
 	}
 
-	if (MDOC_LINEARG_MAX == argc) {
-		mdoc_argv_free(argc - 1, argv);
-		return(mdoc_perr(mdoc, line, ppos, "parameter hard-limit exceeded"));
-	}
-
-	c = mdoc_block_alloc(mdoc, line, ppos, 
-			tok, (size_t)argc, argv);
-	mdoc_argv_free(argc, argv);
-
-	if (0 == c)
+	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, arg))
 		return(0);
 
 	mdoc->next = MDOC_NEXT_CHILD;
@@ -751,7 +872,8 @@ macro_scoped(MACRO_PROT_ARGS)
 	if (0 == buf[*pos]) {
 		if ( ! mdoc_head_alloc(mdoc, line, ppos, tok))
 			return(0);
-		if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok, line, ppos))
+		if ( ! rewind_subblock(MDOC_HEAD, mdoc, 
+					tok, line, ppos))
 			return(0);
 		if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
 			return(0);
@@ -766,13 +888,18 @@ macro_scoped(MACRO_PROT_ARGS)
 	for (;;) {
 		lastarg = *pos;
 		c = mdoc_args(mdoc, line, pos, buf, tok, &p);
-	
+
 		if (ARGS_ERROR == c)
 			return(0);
-		if (ARGS_PUNCT == c)
-			break;
 		if (ARGS_EOLN == c)
 			break;
+		if (ARGS_PHRASE == c) {
+			/*
+			if ( ! mdoc_phrase(mdoc, line, lastarg, buf))
+				return(0);
+			*/
+			continue;
+		}
 
 		/* FIXME: if .It -column, the lookup must be for a
 		 * sub-line component.  BLAH. */
@@ -792,10 +919,10 @@ macro_scoped(MACRO_PROT_ARGS)
 		break;
 	}
 	
-	if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok, line, ppos))
-		return(0);
 	if (1 == ppos && ! append_delims(mdoc, line, pos, buf))
 		return(0);
+	if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok, line, ppos))
+		return(0);
 
 	if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
 		return(0);
@@ -824,13 +951,13 @@ macro_scoped(MACRO_PROT_ARGS)
  *         TEXT (`;')
  *         TEXT (`;')
  */
-int
+static int
 macro_scoped_line(MACRO_PROT_ARGS)
 {
 	int		  lastarg, c;
 	char		  *p;
 
-	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, 0, NULL))
+	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL))
 		return(0);
 	mdoc->next = MDOC_NEXT_CHILD;
 
@@ -847,6 +974,7 @@ macro_scoped_line(MACRO_PROT_ARGS)
 	for (;;) {
 		lastarg = *pos;
 		c = mdoc_args(mdoc, line, pos, buf, tok, &p);
+		assert(ARGS_PHRASE != c);
 
 		if (ARGS_ERROR == c)
 			return(0);
@@ -895,7 +1023,7 @@ macro_scoped_line(MACRO_PROT_ARGS)
  *             TEXT (`b')
  *     TEXT (';')
  */
-int
+static int
 macro_constant_scoped(MACRO_PROT_ARGS)
 {
 	int		  lastarg, flushed, j, c, maxargs;
@@ -913,7 +1041,7 @@ macro_constant_scoped(MACRO_PROT_ARGS)
 		break;
 	}
 
-	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, 0, NULL))
+	if ( ! mdoc_block_alloc(mdoc, line, ppos, tok, NULL))
 		return(0); 
 	mdoc->next = MDOC_NEXT_CHILD;
 
@@ -943,6 +1071,8 @@ macro_constant_scoped(MACRO_PROT_ARGS)
 		}
 
 		c = mdoc_args(mdoc, line, pos, buf, tok, &p);
+		assert(ARGS_PHRASE != c);
+
 		if (ARGS_ERROR == c)
 			return(0);
 		if (ARGS_PUNCT == c)
@@ -954,20 +1084,24 @@ macro_constant_scoped(MACRO_PROT_ARGS)
 			return(0);
 		else if (MDOC_MAX != c) {
 			if ( ! flushed) {
-				if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok, line, ppos))
+				if ( ! rewind_subblock(MDOC_HEAD, mdoc, 
+							tok, line, ppos))
 					return(0);
 				flushed = 1;
-				if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
+				if ( ! mdoc_body_alloc(mdoc, line, 
+							ppos, tok))
 					return(0);
 				mdoc->next = MDOC_NEXT_CHILD;
 			}
-			if ( ! mdoc_macro(mdoc, c, line, lastarg, pos, buf))
+			if ( ! mdoc_macro(mdoc, c, line, lastarg, 
+						pos, buf))
 				return(0);
 			break;
 		}
 
 		if ( ! flushed && mdoc_isdelim(p)) {
-			if ( ! rewind_subblock(MDOC_HEAD, mdoc, tok, line, ppos))
+			if ( ! rewind_subblock(MDOC_HEAD, mdoc, 
+						tok, line, ppos))
 				return(0);
 			flushed = 1;
 			if ( ! mdoc_body_alloc(mdoc, line, ppos, tok))
@@ -1007,26 +1141,28 @@ macro_constant_scoped(MACRO_PROT_ARGS)
  *    ELEMENT (.No)
  *    TEXT (`b')
  */
-int
+static int
 macro_constant_delimited(MACRO_PROT_ARGS)
 {
-	int		  lastarg, flushed, j, c, maxargs, argc;
-	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
+	int		  lastarg, flushed, j, c, maxargs, 
+			  igndelim, ignargs;
+	struct mdoc_arg	 *arg;
 	char		 *p;
 
 	lastarg = ppos;
 	flushed = 0;
+	
+	/* 
+	 * Maximum arguments per macro.  Some of these have none and
+	 * exit as soon as they're parsed.
+	 */
 
 	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:
@@ -1034,29 +1170,53 @@ macro_constant_delimited(MACRO_PROT_ARGS)
 		break;
 	}
 
-	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);
+	/* 
+	 * Whether to ignore delimiter characters.  `Pf' accepts its
+	 * first token as a parameter no matter what it looks like (if
+	 * it's text).
+	 */
+
+	switch (tok) {
+	case (MDOC_Pf):
+		igndelim = 1;
+		break;
+	default:
+		igndelim = 0;
+		break;
 	}
 
-	if (MDOC_LINEARG_MAX == argc) {
-		mdoc_argv_free(argc - 1, argv);
-		return(mdoc_perr(mdoc, line, ppos, "parameter hard-limit exceeded"));
+	/* 
+	 * Whether to ignore arguments: `St', for example, handles its
+	 * argument-like parameters as regular parameters.
+	 */
+
+	switch (tok) {
+	case (MDOC_St):
+		ignargs = 1;
+		break;
+	default:
+		ignargs = 0;
+		break;
 	}
 
-	c = mdoc_elem_alloc(mdoc, line, ppos, tok, argc, argv);
-	mdoc_argv_free(argc, argv);
+	arg = NULL;
+
+	if ( ! ignargs)
+		for (;;) {
+			lastarg = *pos;
+			c = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
+			if (ARGV_EOLN == c)
+				break;
+			if (ARGV_WORD == c) {
+				*pos = lastarg;
+				break;
+			} else if (ARGV_ARG == c)
+				continue;
+			mdoc_argv_free(arg);
+			return(0);
+		}
 
-	if (0 == c)
+	if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
 		return(0);
 
 	mdoc->next = MDOC_NEXT_CHILD;
@@ -1071,6 +1231,8 @@ macro_constant_delimited(MACRO_PROT_ARGS)
 		}
 
 		c = mdoc_args(mdoc, line, pos, buf, tok, &p);
+		assert(ARGS_PHRASE != c);
+
 		if (ARGS_ERROR == c)
 			return(0);
 		if (ARGS_PUNCT == c)
@@ -1089,7 +1251,7 @@ macro_constant_delimited(MACRO_PROT_ARGS)
 			break;
 		}
 
-		if ( ! flushed && mdoc_isdelim(p)) {
+		if ( ! flushed && mdoc_isdelim(p) && ! igndelim) {
 			if ( ! rewind_elem(mdoc, tok))
 				return(0);
 			flushed = 1;
@@ -1113,18 +1275,20 @@ macro_constant_delimited(MACRO_PROT_ARGS)
  * A constant macro is the simplest classification.  It spans an entire
  * line.  
  */
-int
+static int
 macro_constant(MACRO_PROT_ARGS)
 {
-	int		  c, w, la, argc;
-	struct mdoc_arg	  argv[MDOC_LINEARG_MAX];
+	int		  c, w, la;
+	struct mdoc_arg	 *arg;
 	char		 *p;
 
 	assert( ! (MDOC_CALLABLE & mdoc_macros[tok].flags));
 
-	for (argc = 0; argc < MDOC_LINEARG_MAX; argc++) {
+	arg = NULL;
+
+	for (;;) {
 		la = *pos;
-		c = mdoc_argv(mdoc, line, tok, &argv[argc], pos, buf);
+		c = mdoc_argv(mdoc, line, tok, &arg, pos, buf);
 		if (ARGV_EOLN == c) 
 			break;
 		if (ARGV_WORD == c) {
@@ -1132,20 +1296,11 @@ macro_constant(MACRO_PROT_ARGS)
 			break;
 		} else if (ARGV_ARG == c)
 			continue;
-
-		mdoc_argv_free(argc, argv);
+		mdoc_argv_free(arg);
 		return(0);
 	}
 
-	if (MDOC_LINEARG_MAX == argc) {
-		mdoc_argv_free(argc - 1, argv);
-		return(mdoc_perr(mdoc, line, ppos, "parameter hard-limit exceeded"));
-	}
-
-	c = mdoc_elem_alloc(mdoc, line, ppos, tok, argc, argv);
-	mdoc_argv_free(argc, argv);
-
-	if (0 == c)
+	if ( ! mdoc_elem_alloc(mdoc, line, ppos, tok, arg))
 		return(0);
 
 	mdoc->next = MDOC_NEXT_CHILD;
@@ -1153,6 +1308,8 @@ macro_constant(MACRO_PROT_ARGS)
 	for (;;) {
 		la = *pos;
 		w = mdoc_args(mdoc, line, pos, buf, tok, &p);
+		assert(ARGS_PHRASE != c);
+
 		if (ARGS_ERROR == w)
 			return(0);
 		if (ARGS_EOLN == w)
@@ -1178,11 +1335,11 @@ macro_constant(MACRO_PROT_ARGS)
 
 
 /* ARGSUSED */
-int
+static int
 macro_obsolete(MACRO_PROT_ARGS)
 {
 
-	return(mdoc_pwarn(mdoc, line, ppos, WARN_SYNTAX, "macro is obsolete"));
+	return(pwarn(mdoc, line, ppos, WOBS));
 }
 
 
@@ -1209,7 +1366,8 @@ macro_end(struct mdoc *mdoc)
 			continue;
 		if ( ! (MDOC_EXPLICIT & mdoc_macros[n->tok].flags))
 			continue;
-		return(mdoc_nerr(mdoc, n, "macro scope still open on exit"));
+		return(mdoc_nerr(mdoc, n, 
+				"macro scope still open on exit"));
 	}
 
 	return(rewind_last(mdoc, mdoc->first));