+/*
+ * Parse a macro line, that is, a line beginning with the control
+ * character.
+ */
+static int
+mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs)
+{
+ enum mdoct tok;
+ int i, sv;
+ char mac[5];
+ struct mdoc_node *n;
+
+ /* Empty post-control lines are ignored. */
+
+ if ('"' == buf[offs]) {
+ mdoc_pmsg(mdoc, ln, offs, MANDOCERR_BADCOMMENT);
+ return(1);
+ } else if ('\0' == buf[offs])
+ return(1);
+
+ sv = offs;
+
+ /*
+ * Copy the first word into a nil-terminated buffer.
+ * Stop copying when a tab, space, or eoln is encountered.
+ */
+
+ i = 0;
+ while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] &&
+ '\t' != buf[offs])
+ mac[i++] = buf[offs++];
+
+ mac[i] = '\0';
+
+ tok = (i > 1 && i < 4) ? mdoc_hash_find(mac) : MDOC_MAX;
+
+ if (MDOC_MAX == tok) {
+ mandoc_vmsg(MANDOCERR_MACRO, mdoc->parse,
+ ln, sv, "%s", buf + sv - 1);
+ return(1);
+ }
+
+ /* Disregard the first trailing tab, if applicable. */
+
+ if ('\t' == buf[offs])
+ offs++;
+
+ /* Jump to the next non-whitespace word. */
+
+ while (buf[offs] && ' ' == buf[offs])
+ offs++;
+
+ /*
+ * Trailing whitespace. Note that tabs are allowed to be passed
+ * into the parser as "text", so we only warn about spaces here.
+ */
+
+ if ('\0' == buf[offs] && ' ' == buf[offs - 1])
+ mdoc_pmsg(mdoc, ln, offs - 1, MANDOCERR_EOLNSPACE);
+
+ /*
+ * If an initial macro or a list invocation, divert directly
+ * into macro processing.
+ */
+
+ if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok) {
+ if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf))
+ goto err;
+ return(1);
+ }
+
+ n = mdoc->last;
+ assert(mdoc->last);
+
+ /*
+ * If the first macro of a `Bl -column', open an `It' block
+ * context around the parsed macro.
+ */
+
+ if (MDOC_Bl == n->tok && MDOC_BODY == n->type &&
+ LIST_column == n->norm->Bl.type) {
+ mdoc->flags |= MDOC_FREECOL;
+ if ( ! mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf))
+ goto err;
+ return(1);
+ }
+
+ /*
+ * If we're following a block-level `It' within a `Bl -column'
+ * context (perhaps opened in the above block or in ptext()),
+ * then open an `It' block context around the parsed macro.
+ */
+
+ if (MDOC_It == n->tok && MDOC_BLOCK == n->type &&
+ NULL != n->parent &&
+ MDOC_Bl == n->parent->tok &&
+ LIST_column == n->parent->norm->Bl.type) {
+ mdoc->flags |= MDOC_FREECOL;
+ if ( ! mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf))
+ goto err;
+ return(1);
+ }
+
+ /* Normal processing of a macro. */
+
+ if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf))
+ goto err;
+
+ /* In quick mode (for mandocdb), abort after the NAME section. */
+
+ if (mdoc->quick && MDOC_Sh == tok &&
+ SEC_NAME != mdoc->last->sec)
+ return(2);
+
+ return(1);
+
+err: /* Error out. */
+
+ mdoc->flags |= MDOC_HALT;
+ return(0);
+}