+ if (term == '\0') {
+ switch (*++cp) {
+ case '\0':
+ maxl = 0;
+ break;
+ case '(':
+ cp++;
+ maxl = 2;
+ break;
+ case '[':
+ cp++;
+ term = ']';
+ maxl = 0;
+ break;
+ default:
+ maxl = 1;
+ break;
+ }
+ } else {
+ cp += 2;
+ maxl = 0;
+ }
+ stnam = cp;
+
+ /* Advance to the end of the name. */
+
+ naml = 0;
+ arg_complete = 1;
+ while (maxl == 0 || naml < maxl) {
+ if (*cp == '\0') {
+ mandoc_msg(MANDOCERR_ESC_BAD, r->parse,
+ ln, (int)(stesc - buf->buf), stesc);
+ arg_complete = 0;
+ break;
+ }
+ if (maxl == 0 && *cp == term) {
+ cp++;
+ break;
+ }
+ if (*cp++ != '\\' || stesc[1] != 'w') {
+ naml++;
+ continue;
+ }
+ switch (mandoc_escape(&cp, NULL, NULL)) {
+ case ESCAPE_SPECIAL:
+ case ESCAPE_UNICODE:
+ case ESCAPE_NUMBERED:
+ case ESCAPE_OVERSTRIKE:
+ naml++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * Retrieve the replacement string; if it is
+ * undefined, resume searching for escapes.
+ */
+
+ switch (stesc[1]) {
+ case '*':
+ if (arg_complete)
+ res = roff_getstrn(r, stnam, naml);
+ break;
+ case 'B':
+ npos = 0;
+ ubuf[0] = arg_complete &&
+ roff_evalnum(r, ln, stnam, &npos,
+ NULL, ROFFNUM_SCALE) &&
+ stnam + npos + 1 == cp ? '1' : '0';
+ ubuf[1] = '\0';
+ break;
+ case 'n':
+ if (arg_complete)
+ (void)snprintf(ubuf, sizeof(ubuf), "%d",
+ roff_getregn(r, stnam, naml));
+ else
+ ubuf[0] = '\0';
+ break;
+ case 'w':
+ /* use even incomplete args */
+ (void)snprintf(ubuf, sizeof(ubuf), "%d",
+ 24 * (int)naml);
+ break;
+ }
+
+ if (res == NULL) {
+ mandoc_vmsg(MANDOCERR_STR_UNDEF,
+ r->parse, ln, (int)(stesc - buf->buf),
+ "%.*s", (int)naml, stnam);
+ res = "";
+ } else if (buf->sz + strlen(res) > SHRT_MAX) {
+ mandoc_msg(MANDOCERR_ROFFLOOP, r->parse,
+ ln, (int)(stesc - buf->buf), NULL);
+ return ROFF_IGN;
+ }
+
+ /* Replace the escape sequence by the string. */
+
+ *stesc = '\0';
+ buf->sz = mandoc_asprintf(&nbuf, "%s%s%s",
+ buf->buf, res, cp) + 1;
+
+ /* Prepare for the next replacement. */
+
+ start = nbuf + pos;
+ stesc = nbuf + (stesc - buf->buf) + strlen(res);
+ free(buf->buf);
+ buf->buf = nbuf;
+ }
+ return ROFF_CONT;
+}
+
+/*
+ * Process text streams.
+ */
+static enum rofferr
+roff_parsetext(struct buf *buf, int pos, int *offs)
+{
+ size_t sz;
+ const char *start;
+ char *p;
+ int isz;
+ enum mandoc_esc esc;
+
+ /* Spring the input line trap. */
+
+ if (roffit_lines == 1) {
+ isz = mandoc_asprintf(&p, "%s\n.%s", buf->buf, roffit_macro);
+ free(buf->buf);
+ buf->buf = p;
+ buf->sz = isz + 1;
+ *offs = 0;
+ free(roffit_macro);
+ roffit_lines = 0;
+ return ROFF_REPARSE;
+ } else if (roffit_lines > 1)
+ --roffit_lines;
+
+ /* Convert all breakable hyphens into ASCII_HYPH. */
+
+ start = p = buf->buf + pos;
+
+ while (*p != '\0') {
+ sz = strcspn(p, "-\\");
+ p += sz;
+
+ if (*p == '\0')
+ break;
+
+ if (*p == '\\') {
+ /* Skip over escapes. */
+ p++;
+ esc = mandoc_escape((const char **)&p, NULL, NULL);
+ if (esc == ESCAPE_ERROR)
+ break;
+ while (*p == '-')
+ p++;
+ continue;
+ } else if (p == start) {
+ p++;
+ continue;
+ }
+
+ if (isalpha((unsigned char)p[-1]) &&
+ isalpha((unsigned char)p[1]))
+ *p = ASCII_HYPH;
+ p++;
+ }
+ return ROFF_CONT;
+}
+
+enum rofferr
+roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
+{
+ enum rofft t;
+ enum rofferr e;
+ int pos; /* parse point */
+ int spos; /* saved parse point for messages */
+ int ppos; /* original offset in buf->buf */
+ int ctl; /* macro line (boolean) */
+
+ ppos = pos = *offs;
+
+ /* Handle in-line equation delimiters. */
+
+ if (r->tbl == NULL &&
+ r->last_eqn != NULL && r->last_eqn->delim &&
+ (r->eqn == NULL || r->eqn_inline)) {
+ e = roff_eqndelim(r, buf, pos);
+ if (e == ROFF_REPARSE)
+ return e;
+ assert(e == ROFF_CONT);
+ }
+
+ /* Expand some escape sequences. */
+
+ e = roff_res(r, buf, ln, pos);
+ if (e == ROFF_IGN)
+ return e;
+ assert(e == ROFF_CONT);
+
+ ctl = roff_getcontrol(r, buf->buf, &pos);
+
+ /*
+ * First, if a scope is open and we're not a macro, pass the
+ * text through the macro's filter.
+ * Equations process all content themselves.
+ * Tables process almost all content themselves, but we want
+ * to warn about macros before passing it there.
+ */
+
+ if (r->last != NULL && ! ctl) {
+ t = r->last->tok;
+ assert(roffs[t].text);
+ e = (*roffs[t].text)(r, t, buf, ln, pos, pos, offs);
+ assert(e == ROFF_IGN || e == ROFF_CONT);
+ if (e != ROFF_CONT)
+ return e;
+ }
+ if (r->eqn != NULL)
+ return eqn_read(&r->eqn, ln, buf->buf, ppos, offs);
+ if (r->tbl != NULL && ( ! ctl || buf->buf[pos] == '\0'))
+ return tbl_read(r->tbl, ln, buf->buf, ppos);
+ if ( ! ctl)
+ return roff_parsetext(buf, pos, offs);
+
+ /* Skip empty request lines. */
+
+ if (buf->buf[pos] == '"') {
+ mandoc_msg(MANDOCERR_COMMENT_BAD, r->parse,
+ ln, pos, NULL);
+ return ROFF_IGN;
+ } else if (buf->buf[pos] == '\0')
+ return ROFF_IGN;
+
+ /*
+ * If a scope is open, go to the child handler for that macro,
+ * as it may want to preprocess before doing anything with it.
+ * Don't do so if an equation is open.
+ */
+
+ if (r->last) {
+ t = r->last->tok;
+ assert(roffs[t].sub);
+ return (*roffs[t].sub)(r, t, buf, ln, ppos, pos, offs);
+ }
+
+ /* No scope is open. This is a new request or macro. */
+
+ spos = pos;
+ t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+ /* Tables ignore most macros. */
+
+ if (r->tbl != NULL && (t == ROFF_MAX || t == ROFF_TS)) {
+ mandoc_msg(MANDOCERR_TBLMACRO, r->parse,
+ ln, pos, buf->buf + spos);
+ if (t == ROFF_TS)
+ return ROFF_IGN;
+ while (buf->buf[pos] != '\0' && buf->buf[pos] != ' ')
+ pos++;
+ while (buf->buf[pos] != '\0' && buf->buf[pos] == ' ')
+ pos++;
+ return tbl_read(r->tbl, ln, buf->buf, pos);
+ }
+
+ /*
+ * This is neither a roff request nor a user-defined macro.
+ * Let the standard macro set parsers handle it.
+ */
+
+ if (t == ROFF_MAX)
+ return ROFF_CONT;
+
+ /* Execute a roff request or a user defined macro. */
+
+ assert(roffs[t].proc);
+ return (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
+}
+
+void
+roff_endparse(struct roff *r)
+{
+
+ if (r->last)
+ mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+ r->last->line, r->last->col,
+ roffs[r->last->tok].name);
+
+ if (r->eqn) {
+ mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+ r->eqn->eqn.ln, r->eqn->eqn.pos, "EQ");
+ eqn_end(&r->eqn);
+ }
+
+ if (r->tbl) {
+ mandoc_msg(MANDOCERR_BLK_NOEND, r->parse,
+ r->tbl->line, r->tbl->pos, "TS");
+ tbl_end(&r->tbl);
+ }
+}
+
+/*
+ * Parse a roff node's type from the input buffer. This must be in the
+ * form of ".foo xxx" in the usual way.
+ */
+static enum rofft
+roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos)
+{
+ char *cp;
+ const char *mac;
+ size_t maclen;
+ enum rofft t;
+
+ cp = buf + *pos;
+
+ if ('\0' == *cp || '"' == *cp || '\t' == *cp || ' ' == *cp)
+ return ROFF_MAX;
+
+ mac = cp;
+ maclen = roff_getname(r, &cp, ln, ppos);
+
+ t = (r->current_string = roff_getstrn(r, mac, maclen))
+ ? ROFF_USERDEF : roffhash_find(mac, maclen);
+
+ if (ROFF_MAX != t)
+ *pos = cp - buf;
+
+ return t;
+}
+
+/* --- handling of request blocks ----------------------------------------- */
+
+static enum rofferr
+roff_cblock(ROFF_ARGS)
+{
+
+ /*
+ * A block-close `..' should only be invoked as a child of an
+ * ignore macro, otherwise raise a warning and just ignore it.
+ */
+
+ if (r->last == NULL) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "..");
+ return ROFF_IGN;
+ }
+
+ switch (r->last->tok) {
+ case ROFF_am:
+ /* ROFF_am1 is remapped to ROFF_am in roff_block(). */
+ case ROFF_ami:
+ case ROFF_de:
+ /* ROFF_de1 is remapped to ROFF_de in roff_block(). */
+ case ROFF_dei:
+ case ROFF_ig:
+ break;
+ default:
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "..");
+ return ROFF_IGN;
+ }
+
+ if (buf->buf[pos] != '\0')
+ mandoc_vmsg(MANDOCERR_ARG_SKIP, r->parse, ln, pos,
+ ".. %s", buf->buf + pos);
+
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+ return ROFF_IGN;
+
+}
+
+static void
+roffnode_cleanscope(struct roff *r)
+{
+
+ while (r->last) {
+ if (--r->last->endspan != 0)
+ break;
+ roffnode_pop(r);
+ }
+}
+
+static void
+roff_ccond(struct roff *r, int ln, int ppos)
+{
+
+ if (NULL == r->last) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "\\}");
+ return;
+ }
+
+ switch (r->last->tok) {
+ case ROFF_el:
+ case ROFF_ie:
+ case ROFF_if:
+ break;
+ default:
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "\\}");
+ return;
+ }
+
+ if (r->last->endspan > -1) {
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, r->parse,
+ ln, ppos, "\\}");
+ return;
+ }
+
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+ return;
+}
+
+static enum rofferr
+roff_block(ROFF_ARGS)
+{
+ const char *name;
+ char *iname, *cp;
+ size_t namesz;
+
+ /* Ignore groff compatibility mode for now. */
+
+ if (tok == ROFF_de1)
+ tok = ROFF_de;
+ else if (tok == ROFF_dei1)
+ tok = ROFF_dei;
+ else if (tok == ROFF_am1)
+ tok = ROFF_am;
+ else if (tok == ROFF_ami1)
+ tok = ROFF_ami;
+
+ /* Parse the macro name argument. */
+
+ cp = buf->buf + pos;
+ if (tok == ROFF_ig) {
+ iname = NULL;
+ namesz = 0;
+ } else {
+ iname = cp;
+ namesz = roff_getname(r, &cp, ln, ppos);
+ iname[namesz] = '\0';
+ }
+
+ /* Resolve the macro name argument if it is indirect. */
+
+ if (namesz && (tok == ROFF_dei || tok == ROFF_ami)) {
+ if ((name = roff_getstrn(r, iname, namesz)) == NULL) {
+ mandoc_vmsg(MANDOCERR_STR_UNDEF,
+ r->parse, ln, (int)(iname - buf->buf),
+ "%.*s", (int)namesz, iname);
+ namesz = 0;
+ } else
+ namesz = strlen(name);
+ } else
+ name = iname;
+
+ if (namesz == 0 && tok != ROFF_ig) {
+ mandoc_msg(MANDOCERR_REQ_EMPTY, r->parse,
+ ln, ppos, roffs[tok].name);
+ return ROFF_IGN;
+ }
+
+ roffnode_push(r, tok, name, ln, ppos);
+
+ /*
+ * At the beginning of a `de' macro, clear the existing string
+ * with the same name, if there is one. New content will be
+ * appended from roff_block_text() in multiline mode.
+ */
+
+ if (tok == ROFF_de || tok == ROFF_dei)
+ roff_setstrn(&r->strtab, name, namesz, "", 0, 0);
+
+ if (*cp == '\0')
+ return ROFF_IGN;
+
+ /* Get the custom end marker. */
+
+ iname = cp;
+ namesz = roff_getname(r, &cp, ln, ppos);
+
+ /* Resolve the end marker if it is indirect. */
+
+ if (namesz && (tok == ROFF_dei || tok == ROFF_ami)) {
+ if ((name = roff_getstrn(r, iname, namesz)) == NULL) {
+ mandoc_vmsg(MANDOCERR_STR_UNDEF,
+ r->parse, ln, (int)(iname - buf->buf),
+ "%.*s", (int)namesz, iname);
+ namesz = 0;
+ } else
+ namesz = strlen(name);
+ } else
+ name = iname;
+
+ if (namesz)
+ r->last->end = mandoc_strndup(name, namesz);
+
+ if (*cp != '\0')
+ mandoc_vmsg(MANDOCERR_ARG_EXCESS, r->parse,
+ ln, pos, ".%s ... %s", roffs[tok].name, cp);
+
+ return ROFF_IGN;
+}
+
+static enum rofferr
+roff_block_sub(ROFF_ARGS)
+{
+ enum rofft t;
+ int i, j;
+
+ /*
+ * First check whether a custom macro exists at this level. If
+ * it does, then check against it. This is some of groff's
+ * stranger behaviours. If we encountered a custom end-scope
+ * tag and that tag also happens to be a "real" macro, then we
+ * need to try interpreting it again as a real macro. If it's
+ * not, then return ignore. Else continue.
+ */
+
+ if (r->last->end) {
+ for (i = pos, j = 0; r->last->end[j]; j++, i++)
+ if (buf->buf[i] != r->last->end[j])
+ break;
+
+ if (r->last->end[j] == '\0' &&
+ (buf->buf[i] == '\0' ||
+ buf->buf[i] == ' ' ||
+ buf->buf[i] == '\t')) {
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+
+ while (buf->buf[i] == ' ' || buf->buf[i] == '\t')
+ i++;
+
+ pos = i;
+ if (roff_parse(r, buf->buf, &pos, ln, ppos) !=
+ ROFF_MAX)
+ return ROFF_RERUN;
+ return ROFF_IGN;
+ }
+ }
+
+ /*
+ * If we have no custom end-query or lookup failed, then try
+ * pulling it out of the hashtable.
+ */
+
+ t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+ if (t != ROFF_cblock) {
+ if (tok != ROFF_ig)
+ roff_setstr(r, r->last->name, buf->buf + ppos, 2);
+ return ROFF_IGN;
+ }
+
+ assert(roffs[t].proc);
+ return (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
+}
+
+static enum rofferr
+roff_block_text(ROFF_ARGS)
+{
+
+ if (tok != ROFF_ig)
+ roff_setstr(r, r->last->name, buf->buf + pos, 2);
+
+ return ROFF_IGN;
+}
+
+static enum rofferr
+roff_cond_sub(ROFF_ARGS)
+{
+ enum rofft t;
+ char *ep;
+ int rr;
+
+ rr = r->last->rule;
+ roffnode_cleanscope(r);
+ t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+ /*
+ * Fully handle known macros when they are structurally
+ * required or when the conditional evaluated to true.
+ */
+
+ if ((t != ROFF_MAX) &&
+ (rr || roffs[t].flags & ROFFMAC_STRUCT)) {
+ assert(roffs[t].proc);
+ return (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
+ }
+
+ /*
+ * If `\}' occurs on a macro line without a preceding macro,
+ * drop the line completely.
+ */
+
+ ep = buf->buf + pos;
+ if (ep[0] == '\\' && ep[1] == '}')
+ rr = 0;
+
+ /* Always check for the closing delimiter `\}'. */
+
+ while ((ep = strchr(ep, '\\')) != NULL) {
+ if (*(++ep) == '}') {
+ *ep = '&';
+ roff_ccond(r, ln, ep - buf->buf - 1);
+ }
+ if (*ep != '\0')
+ ++ep;
+ }
+ return rr ? ROFF_CONT : ROFF_IGN;
+}
+
+static enum rofferr
+roff_cond_text(ROFF_ARGS)
+{
+ char *ep;
+ int rr;
+
+ rr = r->last->rule;
+ roffnode_cleanscope(r);
+
+ ep = buf->buf + pos;
+ while ((ep = strchr(ep, '\\')) != NULL) {
+ if (*(++ep) == '}') {
+ *ep = '&';
+ roff_ccond(r, ln, ep - buf->buf - 1);
+ }
+ if (*ep != '\0')
+ ++ep;
+ }
+ return rr ? ROFF_CONT : ROFF_IGN;
+}
+
+/* --- handling of numeric and conditional expressions -------------------- */
+
+/*
+ * Parse a single signed integer number. Stop at the first non-digit.
+ * If there is at least one digit, return success and advance the
+ * parse point, else return failure and let the parse point unchanged.
+ * Ignore overflows, treat them just like the C language.
+ */
+static int
+roff_getnum(const char *v, int *pos, int *res, int flags)
+{
+ int myres, scaled, n, p;
+
+ if (NULL == res)
+ res = &myres;
+
+ p = *pos;
+ n = v[p] == '-';
+ if (n || v[p] == '+')
+ p++;
+
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[p]))
+ p++;
+
+ for (*res = 0; isdigit((unsigned char)v[p]); p++)
+ *res = 10 * *res + v[p] - '0';
+ if (p == *pos + n)
+ return 0;
+
+ if (n)
+ *res = -*res;
+
+ /* Each number may be followed by one optional scaling unit. */
+
+ switch (v[p]) {
+ case 'f':
+ scaled = *res * 65536;
+ break;
+ case 'i':
+ scaled = *res * 240;
+ break;
+ case 'c':
+ scaled = *res * 240 / 2.54;
+ break;
+ case 'v':
+ case 'P':
+ scaled = *res * 40;
+ break;
+ case 'm':
+ case 'n':
+ scaled = *res * 24;
+ break;
+ case 'p':
+ scaled = *res * 10 / 3;
+ break;
+ case 'u':
+ scaled = *res;
+ break;
+ case 'M':
+ scaled = *res * 6 / 25;
+ break;
+ default:
+ scaled = *res;
+ p--;
+ break;
+ }
+ if (flags & ROFFNUM_SCALE)
+ *res = scaled;
+
+ *pos = p + 1;
+ return 1;
+}
+
+/*
+ * Evaluate a string comparison condition.
+ * The first character is the delimiter.
+ * Succeed if the string up to its second occurrence
+ * matches the string up to its third occurence.
+ * Advance the cursor after the third occurrence
+ * or lacking that, to the end of the line.
+ */
+static int
+roff_evalstrcond(const char *v, int *pos)
+{
+ const char *s1, *s2, *s3;
+ int match;
+
+ match = 0;
+ s1 = v + *pos; /* initial delimiter */
+ s2 = s1 + 1; /* for scanning the first string */
+ s3 = strchr(s2, *s1); /* for scanning the second string */
+
+ if (NULL == s3) /* found no middle delimiter */
+ goto out;
+
+ while ('\0' != *++s3) {
+ if (*s2 != *s3) { /* mismatch */
+ s3 = strchr(s3, *s1);
+ break;
+ }
+ if (*s3 == *s1) { /* found the final delimiter */
+ match = 1;
+ break;
+ }
+ s2++;
+ }
+
+out:
+ if (NULL == s3)
+ s3 = strchr(s2, '\0');
+ else if (*s3 != '\0')
+ s3++;
+ *pos = s3 - v;
+ return match;
+}
+
+/*
+ * Evaluate an optionally negated single character, numerical,
+ * or string condition.
+ */
+static int
+roff_evalcond(struct roff *r, int ln, char *v, int *pos)
+{
+ char *cp, *name;
+ size_t sz;
+ int number, savepos, wanttrue;
+
+ if ('!' == v[*pos]) {
+ wanttrue = 0;
+ (*pos)++;
+ } else
+ wanttrue = 1;
+
+ switch (v[*pos]) {
+ case '\0':
+ return 0;
+ case 'n':
+ case 'o':
+ (*pos)++;
+ return wanttrue;
+ case 'c':
+ case 'd':
+ case 'e':
+ case 't':
+ case 'v':
+ (*pos)++;
+ return !wanttrue;
+ case 'r':
+ cp = name = v + ++*pos;
+ sz = roff_getname(r, &cp, ln, *pos);
+ *pos = cp - v;
+ return (sz && roff_hasregn(r, name, sz)) == wanttrue;
+ default:
+ break;
+ }
+
+ savepos = *pos;
+ if (roff_evalnum(r, ln, v, pos, &number, ROFFNUM_SCALE))
+ return (number > 0) == wanttrue;
+ else if (*pos == savepos)
+ return roff_evalstrcond(v, pos) == wanttrue;
+ else
+ return 0;
+}
+
+static enum rofferr
+roff_line_ignore(ROFF_ARGS)
+{
+
+ return ROFF_IGN;
+}
+
+static enum rofferr
+roff_insec(ROFF_ARGS)
+{
+
+ mandoc_msg(MANDOCERR_REQ_INSEC, r->parse,
+ ln, ppos, roffs[tok].name);
+ return ROFF_IGN;
+}
+
+static enum rofferr
+roff_unsupp(ROFF_ARGS)
+{
+
+ mandoc_msg(MANDOCERR_REQ_UNSUPP, r->parse,
+ ln, ppos, roffs[tok].name);
+ return ROFF_IGN;
+}
+
+static enum rofferr
+roff_cond(ROFF_ARGS)
+{
+
+ roffnode_push(r, tok, NULL, ln, ppos);
+
+ /*
+ * An `.el' has no conditional body: it will consume the value
+ * of the current rstack entry set in prior `ie' calls or
+ * defaults to DENY.
+ *
+ * If we're not an `el', however, then evaluate the conditional.
+ */
+
+ r->last->rule = tok == ROFF_el ?
+ (r->rstackpos < 0 ? 0 : r->rstack[r->rstackpos--]) :
+ roff_evalcond(r, ln, buf->buf, &pos);
+
+ /*
+ * An if-else will put the NEGATION of the current evaluated
+ * conditional into the stack of rules.
+ */
+
+ if (tok == ROFF_ie) {
+ if (r->rstackpos + 1 == r->rstacksz) {
+ r->rstacksz += 16;
+ r->rstack = mandoc_reallocarray(r->rstack,
+ r->rstacksz, sizeof(int));
+ }
+ r->rstack[++r->rstackpos] = !r->last->rule;
+ }
+
+ /* If the parent has false as its rule, then so do we. */
+
+ if (r->last->parent && !r->last->parent->rule)
+ r->last->rule = 0;
+
+ /*
+ * Determine scope.
+ * If there is nothing on the line after the conditional,
+ * not even whitespace, use next-line scope.
+ */
+
+ if (buf->buf[pos] == '\0') {
+ r->last->endspan = 2;
+ goto out;
+ }
+
+ while (buf->buf[pos] == ' ')
+ pos++;
+
+ /* An opening brace requests multiline scope. */
+
+ if (buf->buf[pos] == '\\' && buf->buf[pos + 1] == '{') {
+ r->last->endspan = -1;
+ pos += 2;
+ while (buf->buf[pos] == ' ')
+ pos++;
+ goto out;
+ }
+
+ /*
+ * Anything else following the conditional causes
+ * single-line scope. Warn if the scope contains
+ * nothing but trailing whitespace.
+ */
+
+ if (buf->buf[pos] == '\0')
+ mandoc_msg(MANDOCERR_COND_EMPTY, r->parse,
+ ln, ppos, roffs[tok].name);
+
+ r->last->endspan = 1;
+
+out:
+ *offs = pos;
+ return ROFF_RERUN;
+}
+
+static enum rofferr
+roff_ds(ROFF_ARGS)
+{
+ char *string;
+ const char *name;
+ size_t namesz;
+
+ /* Ignore groff compatibility mode for now. */
+
+ if (tok == ROFF_ds1)
+ tok = ROFF_ds;
+ else if (tok == ROFF_as1)
+ tok = ROFF_as;
+
+ /*
+ * The first word is the name of the string.
+ * If it is empty or terminated by an escape sequence,
+ * abort the `ds' request without defining anything.
+ */
+
+ name = string = buf->buf + pos;
+ if (*name == '\0')
+ return ROFF_IGN;
+
+ namesz = roff_getname(r, &string, ln, pos);
+ if (name[namesz] == '\\')
+ return ROFF_IGN;
+
+ /* Read past the initial double-quote, if any. */
+ if (*string == '"')
+ string++;
+
+ /* The rest is the value. */
+ roff_setstrn(&r->strtab, name, namesz, string, strlen(string),
+ ROFF_as == tok);
+ return ROFF_IGN;
+}
+
+/*
+ * Parse a single operator, one or two characters long.
+ * If the operator is recognized, return success and advance the
+ * parse point, else return failure and let the parse point unchanged.
+ */
+static int
+roff_getop(const char *v, int *pos, char *res)
+{
+
+ *res = v[*pos];
+
+ switch (*res) {
+ case '+':
+ case '-':
+ case '*':
+ case '/':
+ case '%':
+ case '&':
+ case ':':
+ break;
+ case '<':
+ switch (v[*pos + 1]) {
+ case '=':
+ *res = 'l';
+ (*pos)++;
+ break;
+ case '>':
+ *res = '!';
+ (*pos)++;
+ break;
+ case '?':
+ *res = 'i';
+ (*pos)++;
+ break;
+ default:
+ break;
+ }
+ break;
+ case '>':
+ switch (v[*pos + 1]) {
+ case '=':
+ *res = 'g';
+ (*pos)++;
+ break;
+ case '?':
+ *res = 'a';
+ (*pos)++;
+ break;
+ default:
+ break;
+ }
+ break;
+ case '=':
+ if ('=' == v[*pos + 1])
+ (*pos)++;
+ break;
+ default:
+ return 0;
+ }
+ (*pos)++;
+
+ return *res;
+}
+
+/*
+ * Evaluate either a parenthesized numeric expression
+ * or a single signed integer number.
+ */
+static int
+roff_evalpar(struct roff *r, int ln,
+ const char *v, int *pos, int *res, int flags)
+{
+
+ if ('(' != v[*pos])
+ return roff_getnum(v, pos, res, flags);
+
+ (*pos)++;
+ if ( ! roff_evalnum(r, ln, v, pos, res, flags | ROFFNUM_WHITE))
+ return 0;
+
+ /*
+ * Omission of the closing parenthesis
+ * is an error in validation mode,
+ * but ignored in evaluation mode.
+ */
+
+ if (')' == v[*pos])
+ (*pos)++;
+ else if (NULL == res)
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Evaluate a complete numeric expression.
+ * Proceed left to right, there is no concept of precedence.
+ */
+static int
+roff_evalnum(struct roff *r, int ln, const char *v,
+ int *pos, int *res, int flags)
+{
+ int mypos, operand2;
+ char operator;
+
+ if (NULL == pos) {
+ mypos = 0;
+ pos = &mypos;
+ }
+
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_evalpar(r, ln, v, pos, res, flags))
+ return 0;
+
+ while (1) {
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_getop(v, pos, &operator))
+ break;
+
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if ( ! roff_evalpar(r, ln, v, pos, &operand2, flags))
+ return 0;
+
+ if (flags & ROFFNUM_WHITE)
+ while (isspace((unsigned char)v[*pos]))
+ (*pos)++;
+
+ if (NULL == res)
+ continue;
+
+ switch (operator) {
+ case '+':
+ *res += operand2;
+ break;
+ case '-':
+ *res -= operand2;
+ break;
+ case '*':
+ *res *= operand2;
+ break;
+ case '/':
+ if (operand2 == 0) {
+ mandoc_msg(MANDOCERR_DIVZERO,
+ r->parse, ln, *pos, v);
+ *res = 0;
+ break;
+ }
+ *res /= operand2;
+ break;
+ case '%':
+ if (operand2 == 0) {
+ mandoc_msg(MANDOCERR_DIVZERO,
+ r->parse, ln, *pos, v);
+ *res = 0;
+ break;
+ }
+ *res %= operand2;
+ break;
+ case '<':
+ *res = *res < operand2;
+ break;
+ case '>':
+ *res = *res > operand2;
+ break;
+ case 'l':
+ *res = *res <= operand2;
+ break;
+ case 'g':
+ *res = *res >= operand2;
+ break;
+ case '=':
+ *res = *res == operand2;
+ break;
+ case '!':
+ *res = *res != operand2;
+ break;
+ case '&':
+ *res = *res && operand2;
+ break;
+ case ':':
+ *res = *res || operand2;
+ break;
+ case 'i':
+ if (operand2 < *res)
+ *res = operand2;
+ break;
+ case 'a':
+ if (operand2 > *res)
+ *res = operand2;
+ break;
+ default:
+ abort();
+ }
+ }
+ return 1;
+}
+
+/* --- register management ------------------------------------------------ */
+
+void
+roff_setreg(struct roff *r, const char *name, int val, char sign)
+{
+ struct roffreg *reg;
+
+ /* Search for an existing register with the same name. */
+ reg = r->regtab;
+
+ while (reg && strcmp(name, reg->key.p))
+ reg = reg->next;
+
+ if (NULL == reg) {
+ /* Create a new register. */
+ reg = mandoc_malloc(sizeof(struct roffreg));
+ reg->key.p = mandoc_strdup(name);
+ reg->key.sz = strlen(name);
+ reg->val = 0;
+ reg->next = r->regtab;
+ r->regtab = reg;
+ }
+
+ if ('+' == sign)
+ reg->val += val;
+ else if ('-' == sign)
+ reg->val -= val;
+ else
+ reg->val = val;
+}
+
+/*
+ * Handle some predefined read-only number registers.
+ * For now, return -1 if the requested register is not predefined;
+ * in case a predefined read-only register having the value -1
+ * were to turn up, another special value would have to be chosen.
+ */
+static int
+roff_getregro(const struct roff *r, const char *name)
+{
+
+ switch (*name) {
+ case '$': /* Number of arguments of the last macro evaluated. */
+ return r->argc;
+ case 'A': /* ASCII approximation mode is always off. */
+ return 0;
+ case 'g': /* Groff compatibility mode is always on. */
+ return 1;
+ case 'H': /* Fixed horizontal resolution. */
+ return 24;
+ case 'j': /* Always adjust left margin only. */
+ return 0;
+ case 'T': /* Some output device is always defined. */
+ return 1;
+ case 'V': /* Fixed vertical resolution. */
+ return 40;
+ default:
+ return -1;
+ }
+}
+
+int
+roff_getreg(const struct roff *r, const char *name)
+{
+ struct roffreg *reg;
+ int val;
+
+ if ('.' == name[0] && '\0' != name[1] && '\0' == name[2]) {
+ val = roff_getregro(r, name + 1);
+ if (-1 != val)
+ return val;
+ }
+
+ for (reg = r->regtab; reg; reg = reg->next)
+ if (0 == strcmp(name, reg->key.p))
+ return reg->val;
+
+ return 0;
+}
+
+static int
+roff_getregn(const struct roff *r, const char *name, size_t len)
+{
+ struct roffreg *reg;
+ int val;
+
+ if ('.' == name[0] && 2 == len) {
+ val = roff_getregro(r, name + 1);
+ if (-1 != val)
+ return val;
+ }
+
+ for (reg = r->regtab; reg; reg = reg->next)
+ if (len == reg->key.sz &&
+ 0 == strncmp(name, reg->key.p, len))
+ return reg->val;
+
+ return 0;
+}
+
+static int
+roff_hasregn(const struct roff *r, const char *name, size_t len)
+{
+ struct roffreg *reg;
+ int val;