=================================================================== RCS file: /cvs/mandoc/roff.c,v retrieving revision 1.375 retrieving revision 1.387 diff -u -p -r1.375 -r1.387 --- mandoc/roff.c 2020/08/03 11:02:57 1.375 +++ mandoc/roff.c 2022/05/01 16:22:06 1.387 @@ -1,6 +1,6 @@ -/* $Id: roff.c,v 1.375 2020/08/03 11:02:57 schwarze Exp $ */ +/* $Id: roff.c,v 1.387 2022/05/01 16:22:06 schwarze Exp $ */ /* - * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze + * Copyright (c) 2010-2015, 2017-2022 Ingo Schwarze * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any @@ -227,15 +227,19 @@ static int roff_line_ignore(ROFF_ARGS); static void roff_man_alloc1(struct roff_man *); static void roff_man_free1(struct roff_man *); static int roff_manyarg(ROFF_ARGS); +static int roff_mc(ROFF_ARGS); static int roff_noarg(ROFF_ARGS); static int roff_nop(ROFF_ARGS); static int roff_nr(ROFF_ARGS); static int roff_onearg(ROFF_ARGS); static enum roff_tok roff_parse(struct roff *, char *, int *, int, int); +static int roff_parse_comment(struct roff *, struct buf *, + int, int, char); static int roff_parsetext(struct roff *, struct buf *, int, int *); static int roff_renamed(ROFF_ARGS); +static int roff_req_or_macro(ROFF_ARGS); static int roff_return(ROFF_ARGS); static int roff_rm(ROFF_ARGS); static int roff_rn(ROFF_ARGS); @@ -379,7 +383,7 @@ static struct roffmac roffs[TOKEN_NONE] = { { roff_noarg, NULL, NULL, 0 }, /* fi */ { roff_onearg, NULL, NULL, 0 }, /* ft */ { roff_onearg, NULL, NULL, 0 }, /* ll */ - { roff_onearg, NULL, NULL, 0 }, /* mc */ + { roff_mc, NULL, NULL, 0 }, /* mc */ { roff_noarg, NULL, NULL, 0 }, /* nf */ { roff_onearg, NULL, NULL, 0 }, /* po */ { roff_onearg, NULL, NULL, 0 }, /* rj */ @@ -861,6 +865,7 @@ void roff_man_free(struct roff_man *man) { roff_man_free1(man); + free(man->os_r); free(man); } @@ -1228,6 +1233,98 @@ deroff(char **dest, const struct roff_node *n) /* --- main functions of the roff parser ---------------------------------- */ +static int +roff_parse_comment(struct roff *r, struct buf *buf, int ln, int pos, + char newesc) +{ + struct roff_node *n; /* used for header comments */ + const char *start; /* start of the string to process */ + const char *cp; /* for RCS id parsing */ + char *stesc; /* start of an escape sequence ('\\') */ + char *ep; /* end of comment string */ + int rcsid; /* kind of RCS id seen */ + + for (start = stesc = buf->buf + pos;; stesc++) { + /* The line ends without continuation or comment. */ + if (stesc[0] == '\0') + return ROFF_CONT; + + /* Unescaped byte: skip it. */ + if (stesc[0] != newesc) + continue; + + /* Backslash at end of line requests line continuation. */ + if (stesc[1] == '\0') { + stesc[0] = '\0'; + return ROFF_IGN | ROFF_APPEND; + } + + /* Found a comment: process it. */ + if (stesc[1] == '"' || stesc[1] == '#') + break; + + /* Escaped escape character: skip them both. */ + if (stesc[1] == newesc) + stesc++; + } + + /* Look for an RCS id in the comment. */ + + rcsid = 0; + if ((cp = strstr(stesc + 2, "$" "OpenBSD")) != NULL) { + rcsid = 1 << MANDOC_OS_OPENBSD; + cp += 8; + } else if ((cp = strstr(stesc + 2, "$" "NetBSD")) != NULL) { + rcsid = 1 << MANDOC_OS_NETBSD; + cp += 7; + } + if (cp != NULL && isalnum((unsigned char)*cp) == 0 && + strchr(cp, '$') != NULL) { + if (r->man->meta.rcsids & rcsid) + mandoc_msg(MANDOCERR_RCS_REP, ln, + (int)(stesc - buf->buf) + 2, "%s", stesc + 1); + r->man->meta.rcsids |= rcsid; + } + + /* Warn about trailing whitespace at the end of the comment. */ + + ep = strchr(stesc + 2, '\0') - 1; + if (*ep == '\n') + *ep-- = '\0'; + if (*ep == ' ' || *ep == '\t') + mandoc_msg(MANDOCERR_SPACE_EOL, + ln, (int)(ep - buf->buf), NULL); + + /* Save comments preceding the title macro in the syntax tree. */ + + if (r->options & MPARSE_COMMENT) { + while (*ep == ' ' || *ep == '\t') + ep--; + ep[1] = '\0'; + n = roff_node_alloc(r->man, ln, stesc + 1 - buf->buf, + ROFFT_COMMENT, TOKEN_NONE); + n->string = mandoc_strdup(stesc + 2); + roff_node_append(r->man, n); + n->flags |= NODE_VALID | NODE_ENDED; + r->man->next = ROFF_NEXT_SIBLING; + } + + /* The comment requests line continuation. */ + + if (stesc[1] == '#') { + *stesc = '\0'; + return ROFF_IGN | ROFF_APPEND; + } + + /* Discard the comment including preceding whitespace. */ + + while (stesc > start && stesc[-1] == ' ' && + (stesc == start + 1 || stesc[-2] != '\\')) + stesc--; + *stesc = '\0'; + return ROFF_CONT; +} + /* * In the current line, expand escape sequences that produce parsable * input text. Also check the syntax of the remaining escape sequences, @@ -1238,11 +1335,9 @@ roff_expand(struct roff *r, struct buf *buf, int ln, i { struct mctx *ctx; /* current macro call context */ char ubuf[24]; /* buffer to print the number */ - struct roff_node *n; /* used for header comments */ const char *start; /* start of the string to process */ char *stesc; /* start of an escape sequence ('\\') */ const char *esct; /* type of esccape sequence */ - char *ep; /* end of comment string */ const char *stnam; /* start of the name, after "[(*" */ const char *cp; /* end of the name, e.g. before ']' */ const char *res; /* the string to be substituted */ @@ -1256,98 +1351,15 @@ roff_expand(struct roff *r, struct buf *buf, int ln, i int npos; /* position in numeric expression */ int arg_complete; /* argument not interrupted by eol */ int quote_args; /* true for \\$@, false for \\$* */ - int done; /* no more input available */ int deftype; /* type of definition to paste */ - int rcsid; /* kind of RCS id seen */ enum mandocerr err; /* for escape sequence problems */ char sign; /* increment number register */ char term; /* character terminating the escape */ - /* Search forward for comments. */ - - done = 0; start = buf->buf + pos; - for (stesc = buf->buf + pos; *stesc != '\0'; stesc++) { - if (stesc[0] != newesc || stesc[1] == '\0') - continue; - stesc++; - if (*stesc != '"' && *stesc != '#') - continue; - - /* Comment found, look for RCS id. */ - - rcsid = 0; - if ((cp = strstr(stesc, "$" "OpenBSD")) != NULL) { - rcsid = 1 << MANDOC_OS_OPENBSD; - cp += 8; - } else if ((cp = strstr(stesc, "$" "NetBSD")) != NULL) { - rcsid = 1 << MANDOC_OS_NETBSD; - cp += 7; - } - if (cp != NULL && - isalnum((unsigned char)*cp) == 0 && - strchr(cp, '$') != NULL) { - if (r->man->meta.rcsids & rcsid) - mandoc_msg(MANDOCERR_RCS_REP, ln, - (int)(stesc - buf->buf) + 1, - "%s", stesc + 1); - r->man->meta.rcsids |= rcsid; - } - - /* Handle trailing whitespace. */ - - ep = strchr(stesc--, '\0') - 1; - if (*ep == '\n') { - done = 1; - ep--; - } - if (*ep == ' ' || *ep == '\t') - mandoc_msg(MANDOCERR_SPACE_EOL, - ln, (int)(ep - buf->buf), NULL); - - /* - * Save comments preceding the title macro - * in the syntax tree. - */ - - if (newesc != ASCII_ESC && r->options & MPARSE_COMMENT) { - while (*ep == ' ' || *ep == '\t') - ep--; - ep[1] = '\0'; - n = roff_node_alloc(r->man, - ln, stesc + 1 - buf->buf, - ROFFT_COMMENT, TOKEN_NONE); - n->string = mandoc_strdup(stesc + 2); - roff_node_append(r->man, n); - n->flags |= NODE_VALID | NODE_ENDED; - r->man->next = ROFF_NEXT_SIBLING; - } - - /* Line continuation with comment. */ - - if (stesc[1] == '#') { - *stesc = '\0'; - return ROFF_IGN | ROFF_APPEND; - } - - /* Discard normal comments. */ - - while (stesc > start && stesc[-1] == ' ' && - (stesc == start + 1 || stesc[-2] != '\\')) - stesc--; - *stesc = '\0'; - break; - } - if (stesc == start) - return ROFF_CONT; - stesc--; - - /* Notice the end of the input. */ - - if (*stesc == '\n') { + stesc = strchr(start, '\0') - 1; + if (stesc >= start && *stesc == '\n') *stesc-- = '\0'; - done = 1; - } expand_count = 0; while (stesc >= start) { @@ -1386,21 +1398,17 @@ roff_expand(struct roff *r, struct buf *buf, int ln, i while (stesc > cp) *stesc-- = '\\'; continue; - } else if (stesc[1] != '\0') { - *stesc = '\\'; - } else { + } else if (stesc[1] == '\0') { *stesc-- = '\0'; - if (done) - continue; - else - return ROFF_IGN | ROFF_APPEND; - } + continue; + } else + *stesc = '\\'; /* Decide whether to expand or to check only. */ term = '\0'; cp = stesc + 1; - if (*cp == 'E') + while (*cp == 'E') cp++; esct = cp; switch (*esct) { @@ -1823,7 +1831,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; @@ -1834,6 +1842,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 && @@ -1845,8 +1861,13 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, assert(e == ROFF_CONT); } - /* Expand some escape sequences. */ + /* Handle comments and escape sequences. */ + e = roff_parse_comment(r, buf, ln, pos, r->escape); + if ((e & ROFF_MASK) == ROFF_IGN) + return e; + assert(e == ROFF_CONT); + e = roff_expand(r, buf, ln, pos, r->escape); if ((e & ROFF_MASK) == ROFF_IGN) return e; @@ -1895,7 +1916,6 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, /* * 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) { @@ -1903,19 +1923,27 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, return (*roffs[t].sub)(r, t, buf, ln, ppos, pos, offs); } - /* 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); + return roff_req_or_macro(r, t, buf, ln, spos, pos, offs); +} - /* Tables ignore most macros. */ +/* + * Handle a new request or macro. + * May be called outside any scope or from inside a conditional scope. + */ +static int +roff_req_or_macro(ROFF_ARGS) { - if (r->tbl != NULL && (t == TOKEN_NONE || t == ROFF_TS || - t == ROFF_br || t == ROFF_ce || t == ROFF_rj || t == ROFF_sp)) { + /* For now, tables ignore most macros and some request. */ + + if (r->tbl != NULL && (tok == TOKEN_NONE || tok == ROFF_TS || + tok == ROFF_br || tok == ROFF_ce || tok == ROFF_rj || + tok == ROFF_sp)) { mandoc_msg(MANDOCERR_TBLMACRO, - ln, pos, "%s", buf->buf + spos); - if (t != TOKEN_NONE) + ln, ppos, "%s", buf->buf + ppos); + if (tok != TOKEN_NONE) return ROFF_IGN; while (buf->buf[pos] != '\0' && buf->buf[pos] != ' ') pos++; @@ -1928,9 +1956,9 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, /* For now, let high level macros abort .ce mode. */ - if (ctl && roffce_node != NULL && - (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ || - t == ROFF_TH || t == ROFF_TS)) { + if (roffce_node != NULL && + (tok == TOKEN_NONE || tok == ROFF_Dd || tok == ROFF_EQ || + tok == ROFF_TH || tok == ROFF_TS)) { r->man->last = roffce_node; r->man->next = ROFF_NEXT_SIBLING; roffce_lines = 0; @@ -1942,12 +1970,12 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, * Let the standard macro set parsers handle it. */ - if (t == TOKEN_NONE) + if (tok == TOKEN_NONE) return ROFF_CONT; - /* Execute a roff request or a user defined macro. */ + /* Execute a roff request or a user-defined macro. */ - return (*roffs[t].proc)(r, t, buf, ln, spos, pos, offs); + return (*roffs[tok].proc)(r, tok, buf, ln, ppos, pos, offs); } /* @@ -1990,8 +2018,10 @@ roff_endparse(struct roff *r) } /* - * Parse a roff node's type from the input buffer. This must be in the - * form of ".foo xxx" in the usual way. + * Parse the request or macro name at buf[*pos]. + * Return ROFF_RENAMED, ROFF_USERDEF, or a ROFF_* token value. + * For empty, undefined, mdoc(7), and man(7) macros, return TOKEN_NONE. + * As a side effect, set r->current_string to the definition or to NULL. */ static enum roff_tok roff_parse(struct roff *r, char *buf, int *pos, int ln, int ppos) @@ -2266,12 +2296,8 @@ roff_block_sub(ROFF_ARGS) 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 a custom end marker is a user-defined or predefined macro + * or a request, interpret it. */ if (r->last->end) { @@ -2297,20 +2323,17 @@ roff_block_sub(ROFF_ARGS) } } - /* - * If we have no custom end-query or lookup failed, then try - * pulling it out of the hashtable. - */ + /* Handle the standard end marker. */ t = roff_parse(r, buf->buf, &pos, ln, ppos); + if (t == ROFF_cblock) + return roff_cblock(r, t, buf, ln, ppos, pos, offs); - if (t != ROFF_cblock) { - if (tok != ROFF_ig) - roff_setstr(r, r->last->name, buf->buf + ppos, 2); - return ROFF_IGN; - } + /* Not an end marker, so append the line to the block. */ - return (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs); + if (tok != ROFF_ig) + roff_setstr(r, r->last->name, buf->buf + ppos, 2); + return ROFF_IGN; } static int @@ -2362,7 +2385,9 @@ roff_cond_checkend(ROFF_ARGS) while ((ep = strchr(ep, '\\')) != NULL) { switch (ep[1]) { case '}': - if (rr) + if (ep[2] == '\0') + ep[0] = '\0'; + else if (rr) ep[1] = '&'; else memmove(ep, ep + 2, strlen(ep + 2) + 1); @@ -2388,27 +2413,18 @@ static int roff_cond_sub(ROFF_ARGS) { struct roffnode *bl; - int irc, rr; + int irc, rr, spos; enum roff_tok t; rr = 0; /* If arguments follow "\}", skip them. */ irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr); + spos = pos; t = roff_parse(r, buf->buf, &pos, ln, ppos); - /* For now, let high level macros abort .ce mode. */ - - 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; - } - /* - * Fully handle known macros when they are structurally - * required or when the conditional evaluated to true. + * Handle requests and macros if the conditional evaluated + * to true or if they are structurally required. + * The .break request is always handled specially. */ if (t == ROFF_break) { @@ -2421,11 +2437,11 @@ roff_cond_sub(ROFF_ARGS) break; } } - } else if (t != TOKEN_NONE && - (rr || roffs[t].flags & ROFFMAC_STRUCT)) - irc |= (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs); - else - irc |= rr ? ROFF_CONT : ROFF_IGN; + } else if (rr || (t < TOKEN_NONE && roffs[t].flags & ROFFMAC_STRUCT)) { + irc |= roff_req_or_macro(r, t, buf, ln, spos, pos, offs); + if (irc & ROFF_WHILE) + irc &= ~(ROFF_LOOPCONT | ROFF_LOOPEXIT); + } return irc; } @@ -3657,7 +3673,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; @@ -3717,6 +3735,54 @@ roff_eo(ROFF_ARGS) } static int +roff_mc(ROFF_ARGS) +{ + struct roff_node *n; + char *cp; + + /* Parse the first argument. */ + + cp = buf->buf + pos; + if (*cp != '\0') + cp++; + if (buf->buf[pos] == '\\') { + switch (mandoc_escape((const char **)&cp, NULL, NULL)) { + case ESCAPE_SPECIAL: + case ESCAPE_UNICODE: + case ESCAPE_NUMBERED: + break; + default: + *cp = '\0'; + mandoc_msg(MANDOCERR_MC_ESC, ln, pos, + "mc %s", buf->buf + pos); + buf->buf[pos] = '\0'; + break; + } + } + + /* Ignore additional arguments. */ + + while (*cp == ' ') + *cp++ = '\0'; + if (*cp != '\0') { + mandoc_msg(MANDOCERR_MC_DIST, ln, (int)(cp - buf->buf), + "mc ... %s", cp); + *cp = '\0'; + } + + /* Create the .mc node. */ + + roff_elem_alloc(r->man, ln, ppos, tok); + n = r->man->last; + if (buf->buf[pos] != '\0') + roff_word_alloc(r->man, ln, pos, buf->buf + pos); + n->flags |= NODE_LINE | NODE_VALID | NODE_ENDED; + r->man->last = n; + r->man->next = ROFF_NEXT_SIBLING; + return ROFF_IGN; +} + +static int roff_nop(ROFF_ARGS) { while (buf->buf[pos] == ' ') @@ -3857,8 +3923,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) { @@ -3873,9 +3940,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++) @@ -3937,9 +4008,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,