=================================================================== RCS file: /cvs/mandoc/roff.c,v retrieving revision 1.362 retrieving revision 1.383 diff -u -p -r1.362 -r1.383 --- mandoc/roff.c 2019/02/06 17:40:13 1.362 +++ mandoc/roff.c 2022/04/24 17:40:22 1.383 @@ -1,7 +1,7 @@ -/* $Id: roff.c,v 1.362 2019/02/06 17:40:13 schwarze Exp $ */ +/* $Id: roff.c,v 1.383 2022/04/24 17:40:22 schwarze Exp $ */ /* + * Copyright (c) 2010-2015, 2017-2022 Ingo Schwarze * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons - * Copyright (c) 2010-2015, 2017-2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,6 +14,8 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Implementation of the roff(7) parser for mandoc(1). */ #include "config.h" @@ -133,15 +135,18 @@ struct roff { char escape; /* escape character */ }; +/* + * A macro definition, condition, or ignored block. + */ struct roffnode { enum roff_tok tok; /* type of node */ struct roffnode *parent; /* up one in stack */ int line; /* parse line */ int col; /* parse col */ char *name; /* node name, e.g. macro name */ - char *end; /* end-rules: custom token */ - int endspan; /* end-rules: next-line or infty */ - int rule; /* current evaluation rule */ + char *end; /* custom end macro of the block */ + int endspan; /* scope to: 1=eol 2=next line -1=\} */ + int rule; /* content is: 1=evaluated 0=skipped */ }; #define ROFF_ARGS struct roff *r, /* parse ctx */ \ @@ -181,18 +186,20 @@ static int roff_als(ROFF_ARGS); static int roff_block(ROFF_ARGS); static int roff_block_text(ROFF_ARGS); static int roff_block_sub(ROFF_ARGS); +static int roff_break(ROFF_ARGS); static int roff_cblock(ROFF_ARGS); static int roff_cc(ROFF_ARGS); static int roff_ccond(struct roff *, int, int); static int roff_char(ROFF_ARGS); static int roff_cond(ROFF_ARGS); +static int roff_cond_checkend(ROFF_ARGS); static int roff_cond_text(ROFF_ARGS); static int roff_cond_sub(ROFF_ARGS); static int roff_ds(ROFF_ARGS); static int roff_ec(ROFF_ARGS); static int roff_eo(ROFF_ARGS); static int roff_eqndelim(struct roff *, struct buf *, int); -static int roff_evalcond(struct roff *r, int, char *, int *); +static int roff_evalcond(struct roff *, int, char *, int *); static int roff_evalnum(struct roff *, int, const char *, int *, int *, int); static int roff_evalpar(struct roff *, int, @@ -351,7 +358,7 @@ const char *__roff_name[MAN_MAX + 1] = { "Lk", "Mt", "Brq", "Bro", "Brc", "%C", "Es", "En", "Dx", "%Q", "%U", "Ta", - NULL, + "Tg", NULL, "TH", "SH", "SS", "TP", "TQ", "LP", "PP", "P", "IP", @@ -400,7 +407,7 @@ static struct roffmac roffs[TOKEN_NONE] = { { roff_unsupp, NULL, NULL, 0 }, /* boxa */ { roff_line_ignore, NULL, NULL, 0 }, /* bp */ { roff_unsupp, NULL, NULL, 0 }, /* BP */ - { roff_unsupp, NULL, NULL, 0 }, /* break */ + { roff_break, NULL, NULL, 0 }, /* break */ { roff_line_ignore, NULL, NULL, 0 }, /* breakchar */ { roff_line_ignore, NULL, NULL, 0 }, /* brnl */ { roff_noarg, NULL, NULL, 0 }, /* brp */ @@ -685,7 +692,7 @@ roffhash_find(struct ohash *htab, const char *name, si /* * Pop the current node off of the stack of roff instructions currently - * pending. + * pending. Return 1 if it is a loop or 0 otherwise. */ static int roffnode_pop(struct roff *r) @@ -767,6 +774,7 @@ void roff_reset(struct roff *r) { roff_free1(r); + r->options |= MPARSE_COMMENT; r->format = r->options & (MPARSE_MDOC | MPARSE_MAN); r->control = '\0'; r->escape = '\\'; @@ -779,7 +787,7 @@ roff_reset(struct roff *r) void roff_free(struct roff *r) { - int i; + int i; roff_free1(r); for (i = 0; i < r->mstacksz; i++) @@ -796,7 +804,7 @@ roff_alloc(int options) r = mandoc_calloc(1, sizeof(struct roff)); r->reqtab = roffhash_alloc(0, ROFF_RENAMED); - r->options = options; + r->options = options | MPARSE_COMMENT; r->format = options & (MPARSE_MDOC | MPARSE_MAN); r->mstackpos = -1; r->rstackpos = -1; @@ -853,6 +861,7 @@ void roff_man_free(struct roff_man *man) { roff_man_free1(man); + free(man->os_r); free(man); } @@ -1096,6 +1105,7 @@ roff_node_free(struct roff_node *n) free(n->norm); eqn_box_free(n->eqn); free(n->string); + free(n->tag); free(n); } @@ -1109,13 +1119,72 @@ roff_node_delete(struct roff_man *man, struct roff_nod roff_node_free(n); } +int +roff_node_transparent(struct roff_node *n) +{ + if (n == NULL) + return 0; + if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) + return 1; + return roff_tok_transparent(n->tok); +} + +int +roff_tok_transparent(enum roff_tok tok) +{ + switch (tok) { + case ROFF_ft: + case ROFF_ll: + case ROFF_mc: + case ROFF_po: + case ROFF_ta: + case MDOC_Db: + case MDOC_Es: + case MDOC_Sm: + case MDOC_Tg: + case MAN_DT: + case MAN_UC: + case MAN_PD: + case MAN_AT: + return 1; + default: + return 0; + } +} + +struct roff_node * +roff_node_child(struct roff_node *n) +{ + for (n = n->child; roff_node_transparent(n); n = n->next) + continue; + return n; +} + +struct roff_node * +roff_node_prev(struct roff_node *n) +{ + do { + n = n->prev; + } while (roff_node_transparent(n)); + return n; +} + +struct roff_node * +roff_node_next(struct roff_node *n) +{ + do { + n = n->next; + } while (roff_node_transparent(n)); + return n; +} + void deroff(char **dest, const struct roff_node *n) { char *cp; size_t sz; - if (n->type != ROFFT_TEXT) { + if (n->string == NULL) { for (n = n->child; n != NULL; n = n->next) deroff(dest, n); return; @@ -1242,7 +1311,7 @@ roff_expand(struct roff *r, struct buf *buf, int ln, i * in the syntax tree. */ - if (newesc != ASCII_ESC && r->format == 0) { + if (newesc != ASCII_ESC && r->options & MPARSE_COMMENT) { while (*ep == ' ' || *ep == '\t') ep--; ep[1] = '\0'; @@ -1332,7 +1401,7 @@ roff_expand(struct roff *r, struct buf *buf, int ln, i term = '\0'; cp = stesc + 1; - if (*cp == 'E') + while (*cp == 'E') cp++; esct = cp; switch (*esct) { @@ -1586,7 +1655,7 @@ char * roff_getarg(struct roff *r, char **cpp, int ln, int *pos) { struct buf buf; - char *cp, *start; + char *cp, *start; int newesc, pairs, quoted, white; /* Quoting can only start with a new word. */ @@ -1755,7 +1824,7 @@ roff_parsetext(struct roff *r, struct buf *buf, int po } int -roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs) +roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs, size_t len) { enum roff_tok t; int e; @@ -1766,6 +1835,14 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, ppos = pos = *offs; + if (len > 80 && r->tbl == NULL && r->eqn == NULL && + (r->man->flags & ROFF_NOFILL) == 0 && + strchr(" .\\", buf->buf[pos]) == NULL && + buf->buf[pos] != r->control && + strcspn(buf->buf, " ") < 80) + mandoc_msg(MANDOCERR_TEXT_LONG, ln, (int)len - 1, + "%.20s...", buf->buf + pos); + /* Handle in-line equation delimiters. */ if (r->tbl == NULL && @@ -1811,8 +1888,10 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, roff_addtbl(r->man, ln, r->tbl); return e; } - if ( ! ctl) + if ( ! ctl) { + r->options &= ~MPARSE_COMMENT; return roff_parsetext(r, buf, pos, offs) | e; + } /* Skip empty request lines. */ @@ -1835,6 +1914,7 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, /* No scope is open. This is a new request or macro. */ + r->options &= ~MPARSE_COMMENT; spos = pos; t = roff_parse(r, buf->buf, &pos, ln, ppos); @@ -1964,15 +2044,14 @@ roff_parse(struct roff *r, char *buf, int *pos, int ln /* --- handling of request blocks ----------------------------------------- */ +/* + * Close a macro definition block or an "ignore" block. + */ static int roff_cblock(ROFF_ARGS) { + int rr; - /* - * 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, ln, ppos, ".."); return ROFF_IGN; @@ -1980,35 +2059,51 @@ roff_cblock(ROFF_ARGS) 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; + case ROFF_am1: + case ROFF_de1: + /* Remapped in roff_block(). */ + abort(); default: mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, ".."); return ROFF_IGN; } + roffnode_pop(r); + roffnode_cleanscope(r); + + /* + * If a conditional block with braces is still open, + * check for "\}" block end markers. + */ + + if (r->last != NULL && r->last->endspan < 0) { + rr = 1; /* If arguments follow "\}", warn about them. */ + roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr); + } + if (buf->buf[pos] != '\0') mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos, ".. %s", buf->buf + pos); - roffnode_pop(r); - roffnode_cleanscope(r); return ROFF_IGN; - } +/* + * Pop all nodes ending at the end of the current input line. + * Return the number of loops ended. + */ static int roffnode_cleanscope(struct roff *r) { int inloop; inloop = 0; - while (r->last != NULL) { + while (r->last != NULL && r->last->endspan > 0) { if (--r->last->endspan != 0) break; inloop += roffnode_pop(r); @@ -2016,6 +2111,11 @@ roffnode_cleanscope(struct roff *r) return inloop; } +/* + * Handle the closing "\}" of a conditional block. + * Apart from generating warnings, this only pops nodes. + * Return the number of loops ended. + */ static int roff_ccond(struct roff *r, int ln, int ppos) { @@ -2232,12 +2332,20 @@ roff_block_text(ROFF_ARGS) return ROFF_IGN; } +/* + * Check for a closing "\}" and handle it. + * In this function, the final "int *offs" argument is used for + * different purposes than elsewhere: + * Input: *offs == 0: caller wants to discard arguments following \} + * *offs == 1: caller wants to preserve text following \} + * Output: *offs = 0: tell caller to discard input line + * *offs = 1: tell caller to use input line + */ static int -roff_cond_sub(ROFF_ARGS) +roff_cond_checkend(ROFF_ARGS) { char *ep; int endloop, irc, rr; - enum roff_tok t; irc = ROFF_IGN; rr = r->last->rule; @@ -2247,23 +2355,28 @@ roff_cond_sub(ROFF_ARGS) irc |= endloop; /* - * If `\}' occurs on a macro line without a preceding macro, - * drop the line completely. + * If "\}" occurs on a macro line without a preceding macro or + * a text line contains nothing else, drop the line completely. */ ep = buf->buf + pos; - if (ep[0] == '\\' && ep[1] == '}') + if (ep[0] == '\\' && ep[1] == '}' && (ep[2] == '\0' || *offs == 0)) rr = 0; /* - * The closing delimiter `\}' rewinds the conditional scope + * The closing delimiter "\}" rewinds the conditional scope * but is otherwise ignored when interpreting the line. */ while ((ep = strchr(ep, '\\')) != NULL) { switch (ep[1]) { case '}': - memmove(ep, ep + 2, strlen(ep + 2) + 1); + if (ep[2] == '\0') + ep[0] = '\0'; + else if (rr) + ep[1] = '&'; + else + memmove(ep, ep + 2, strlen(ep + 2) + 1); if (roff_ccond(r, ln, ep - buf->buf)) irc |= endloop; break; @@ -2275,61 +2388,70 @@ roff_cond_sub(ROFF_ARGS) break; } } - - /* - * Fully handle known macros when they are structurally - * required or when the conditional evaluated to true. - */ - - t = roff_parse(r, buf->buf, &pos, ln, ppos); - irc |= t != TOKEN_NONE && (rr || roffs[t].flags & ROFFMAC_STRUCT) ? - (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs) : - rr ? ROFF_CONT : ROFF_IGN; + *offs = rr; return irc; } +/* + * Parse and process a request or macro line in conditional scope. + */ static int -roff_cond_text(ROFF_ARGS) +roff_cond_sub(ROFF_ARGS) { - char *ep; - int endloop, irc, rr; + struct roffnode *bl; + int irc, rr; + enum roff_tok t; - irc = ROFF_IGN; - rr = r->last->rule; - endloop = tok != ROFF_while ? ROFF_IGN : - rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT; - if (roffnode_cleanscope(r)) - irc |= endloop; + rr = 0; /* If arguments follow "\}", skip them. */ + irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr); + t = roff_parse(r, buf->buf, &pos, ln, ppos); - /* - * If `\}' occurs on a text line with neither preceding - * nor following characters, drop the line completely. - */ + /* For now, let high level macros abort .ce mode. */ - ep = buf->buf + pos; - if (strcmp(ep, "\\}") == 0) - rr = 0; + if (roffce_node != NULL && + (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ || + t == ROFF_TH || t == ROFF_TS)) { + r->man->last = roffce_node; + r->man->next = ROFF_NEXT_SIBLING; + roffce_lines = 0; + roffce_node = NULL; + } /* - * The closing delimiter `\}' rewinds the conditional scope - * but is otherwise ignored when interpreting the line. + * Fully handle known macros when they are structurally + * required or when the conditional evaluated to true. */ - while ((ep = strchr(ep, '\\')) != NULL) { - switch (ep[1]) { - case '}': - memmove(ep, ep + 2, strlen(ep + 2) + 1); - if (roff_ccond(r, ln, ep - buf->buf)) - irc |= endloop; - break; - case '\0': - ++ep; - break; - default: - ep += 2; - break; + if (t == ROFF_break) { + if (irc & ROFF_LOOPMASK) + irc = ROFF_IGN | ROFF_LOOPEXIT; + else if (rr) { + for (bl = r->last; bl != NULL; bl = bl->parent) { + bl->rule = 0; + if (bl->tok == ROFF_while) + break; + } } - } + } else if (t != TOKEN_NONE && + (rr || roffs[t].flags & ROFFMAC_STRUCT)) { + irc |= (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs); + if (irc & ROFF_WHILE) + irc &= ~(ROFF_LOOPCONT | ROFF_LOOPEXIT); + } else + irc |= rr ? ROFF_CONT : ROFF_IGN; + return irc; +} + +/* + * Parse and process a text line in conditional scope. + */ +static int +roff_cond_text(ROFF_ARGS) +{ + int irc, rr; + + rr = 1; /* If arguments follow "\}", preserve them. */ + irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr); if (rr) irc |= ROFF_CONT; return irc; @@ -2537,7 +2659,7 @@ roff_evalcond(struct roff *r, int ln, char *v, int *po roff_getstrn(r, name, sz, &deftype); istrue = !!deftype; } - *pos = cp - v; + *pos = (name + sz) - v; return istrue == wanttrue; default: break; @@ -2683,8 +2805,15 @@ roff_ds(ROFF_ARGS) return ROFF_IGN; namesz = roff_getname(r, &string, ln, pos); - if (name[namesz] == '\\') + switch (name[namesz]) { + case '\\': return ROFF_IGN; + case '\t': + string = buf->buf + pos + namesz; + break; + default: + break; + } /* Read past the initial double-quote, if any. */ if (*string == '"') @@ -3060,7 +3189,7 @@ roff_nr(ROFF_ARGS) return ROFF_IGN; keysz = roff_getname(r, &val, ln, pos); - if (key[keysz] == '\\') + if (key[keysz] == '\\' || key[keysz] == '\t') return ROFF_IGN; sign = *val; @@ -3124,7 +3253,7 @@ roff_rm(ROFF_ARGS) namesz = roff_getname(r, &cp, ln, (int)(cp - buf->buf)); roff_setstrn(&r->strtab, name, namesz, NULL, 0, 0); roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0); - if (name[namesz] == '\\') + if (name[namesz] == '\\' || name[namesz] == '\t') break; } return ROFF_IGN; @@ -3459,7 +3588,7 @@ roff_als(ROFF_ARGS) return ROFF_IGN; newsz = roff_getname(r, &oldn, ln, pos); - if (newn[newsz] == '\\' || *oldn == '\0') + if (newn[newsz] == '\\' || newn[newsz] == '\t' || *oldn == '\0') return ROFF_IGN; end = oldn; @@ -3475,7 +3604,18 @@ roff_als(ROFF_ARGS) return ROFF_IGN; } +/* + * The .break request only makes sense inside conditionals, + * and that case is already handled in roff_cond_sub(). + */ static int +roff_break(ROFF_ARGS) +{ + mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, pos, "break"); + return ROFF_IGN; +} + +static int roff_cc(ROFF_ARGS) { const char *p; @@ -3530,7 +3670,9 @@ roff_char(ROFF_ARGS) case ESCAPE_FONTITALIC: case ESCAPE_FONTBOLD: case ESCAPE_FONTBI: - case ESCAPE_FONTCW: + case ESCAPE_FONTCR: + case ESCAPE_FONTCB: + case ESCAPE_FONTCI: case ESCAPE_FONTPREV: font++; break; @@ -3689,7 +3831,7 @@ roff_rn(ROFF_ARGS) return ROFF_IGN; oldsz = roff_getname(r, &newn, ln, pos); - if (oldn[oldsz] == '\\' || *newn == '\0') + if (oldn[oldsz] == '\\' || oldn[oldsz] == '\t' || *newn == '\0') return ROFF_IGN; end = newn; @@ -3730,8 +3872,9 @@ static int roff_shift(ROFF_ARGS) { struct mctx *ctx; - int levels, i; + int argpos, levels, i; + argpos = pos; levels = 1; if (buf->buf[pos] != '\0' && roff_evalnum(r, ln, buf->buf, &pos, &levels, 0) == 0) { @@ -3746,9 +3889,13 @@ roff_shift(ROFF_ARGS) ctx = r->mstack + r->mstackpos; if (levels > ctx->argc) { mandoc_msg(MANDOCERR_SHIFT, - ln, pos, "%d, but max is %d", levels, ctx->argc); + ln, argpos, "%d, but max is %d", levels, ctx->argc); levels = ctx->argc; } + if (levels < 0) { + mandoc_msg(MANDOCERR_ARG_NEG, ln, argpos, "shift %d", levels); + levels = 0; + } if (levels == 0) return ROFF_IGN; for (i = 0; i < levels; i++) @@ -3797,6 +3944,11 @@ roff_userdef(ROFF_ARGS) char *arg, *ap, *dst, *src; size_t sz; + /* If the macro is empty, ignore it altogether. */ + + if (*r->current_string == '\0') + return ROFF_IGN; + /* Initialize a new macro stack context. */ if (++r->mstackpos == r->mstacksz) { @@ -3805,9 +3957,7 @@ roff_userdef(ROFF_ARGS) r->mstacksz += 8; } ctx = r->mstack + r->mstackpos; - ctx->argsz = 0; ctx->argc = 0; - ctx->argv = NULL; /* * Collect pointers to macro argument strings, @@ -3844,7 +3994,7 @@ roff_userdef(ROFF_ARGS) buf->sz = strlen(buf->buf) + 1; *offs = 0; - return buf->sz > 1 && buf->buf[buf->sz - 2] == '\n' ? + return buf->buf[buf->sz - 2] == '\n' ? ROFF_REPARSE | ROFF_USERCALL : ROFF_IGN | ROFF_APPEND; } @@ -3883,8 +4033,12 @@ roff_getname(struct roff *r, char **cpp, int ln, int p for (cp = name; 1; cp++) { namesz = cp - name; - if (*cp == '\0' || *cp == ' ') + if (*cp == '\0') break; + if (*cp == ' ' || *cp == '\t') { + cp++; + break; + } if (*cp != '\\') continue; if (cp[1] == '{' || cp[1] == '}')