=================================================================== RCS file: /cvs/mandoc/roff.c,v retrieving revision 1.355 retrieving revision 1.393 diff -u -p -r1.355 -r1.393 --- mandoc/roff.c 2018/12/21 17:15:19 1.355 +++ mandoc/roff.c 2022/06/03 12:15:55 1.393 @@ -1,7 +1,7 @@ -/* $Id: roff.c,v 1.355 2018/12/21 17:15:19 schwarze Exp $ */ +/* $Id: roff.c,v 1.393 2022/06/03 12:15:55 schwarze Exp $ */ /* + * Copyright (c) 2010-2015, 2017-2022 Ingo Schwarze * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons - * Copyright (c) 2010-2015, 2017, 2018 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,19 +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_br(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, @@ -201,6 +207,8 @@ static int roff_evalpar(struct roff *, int, static int roff_evalstrcond(const char *, int *); static int roff_expand(struct roff *, struct buf *, int, int, char); +static void roff_expand_patch(struct buf *, int, + const char *, int); static void roff_free1(struct roff *); static void roff_freereg(struct roffreg *); static void roff_freestr(struct roffkv *); @@ -221,14 +229,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); @@ -257,8 +270,9 @@ static int roff_userdef(ROFF_ARGS); #define ROFFNUM_WHITE (1 << 1) /* Skip whitespace in roff_evalnum(). */ const char *__roff_name[MAN_MAX + 1] = { - "br", "ce", "ft", "ll", - "mc", "po", "rj", "sp", + "br", "ce", "fi", "ft", + "ll", "mc", "nf", + "po", "rj", "sp", "ta", "ti", NULL, "ab", "ad", "af", "aln", "als", "am", "am1", "ami", @@ -350,14 +364,13 @@ 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", "HP", "SM", "SB", "BI", "IB", "BR", "RB", "R", "B", "I", "IR", "RI", - "nf", "fi", "RE", "RS", "DT", "UC", "PD", "AT", "in", "SY", "YS", "OP", @@ -367,11 +380,13 @@ const char *__roff_name[MAN_MAX + 1] = { const char *const *roff_name = __roff_name; static struct roffmac roffs[TOKEN_NONE] = { - { roff_br, NULL, NULL, 0 }, /* br */ + { roff_noarg, NULL, NULL, 0 }, /* br */ { roff_onearg, NULL, NULL, 0 }, /* ce */ + { 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 */ { roff_onearg, NULL, NULL, 0 }, /* sp */ @@ -398,10 +413,10 @@ 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_br, NULL, NULL, 0 }, /* brp */ + { roff_noarg, NULL, NULL, 0 }, /* brp */ { roff_line_ignore, NULL, NULL, 0 }, /* brpnl */ { roff_unsupp, NULL, NULL, 0 }, /* c2 */ { roff_cc, NULL, NULL, 0 }, /* cc */ @@ -683,7 +698,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) @@ -765,6 +780,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 = '\\'; @@ -777,7 +793,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++) @@ -794,7 +810,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; @@ -807,9 +823,8 @@ roff_alloc(int options) static void roff_man_free1(struct roff_man *man) { - - if (man->first != NULL) - roff_node_delete(man, man->first); + if (man->meta.first != NULL) + roff_node_delete(man, man->meta.first); free(man->meta.msec); free(man->meta.vol); free(man->meta.os); @@ -817,27 +832,33 @@ roff_man_free1(struct roff_man *man) free(man->meta.title); free(man->meta.name); free(man->meta.date); + free(man->meta.sodest); } -static void -roff_man_alloc1(struct roff_man *man) +void +roff_state_reset(struct roff_man *man) { - - memset(&man->meta, 0, sizeof(man->meta)); - man->first = mandoc_calloc(1, sizeof(*man->first)); - man->first->type = ROFFT_ROOT; - man->last = man->first; + man->last = man->meta.first; man->last_es = NULL; man->flags = 0; - man->macroset = MACROSET_NONE; man->lastsec = man->lastnamed = SEC_NONE; man->next = ROFF_NEXT_CHILD; + roff_setreg(man->roff, "nS", 0, '='); } +static void +roff_man_alloc1(struct roff_man *man) +{ + memset(&man->meta, 0, sizeof(man->meta)); + man->meta.first = mandoc_calloc(1, sizeof(*man->meta.first)); + man->meta.first->type = ROFFT_ROOT; + man->meta.macroset = MACROSET_NONE; + roff_state_reset(man); +} + void roff_man_reset(struct roff_man *man) { - roff_man_free1(man); roff_man_alloc1(man); } @@ -845,8 +866,8 @@ roff_man_reset(struct roff_man *man) void roff_man_free(struct roff_man *man) { - roff_man_free1(man); + free(man->os_r); free(man); } @@ -883,6 +904,10 @@ roff_node_alloc(struct roff_man *man, int line, int po n->flags |= NODE_SYNPRETTY; else n->flags &= ~NODE_SYNPRETTY; + if ((man->flags & (ROFF_NOFILL | ROFF_NONOFILL)) == ROFF_NOFILL) + n->flags |= NODE_NOFILL; + else + n->flags &= ~NODE_NOFILL; if (man->flags & MDOC_NEWLINE) n->flags |= NODE_LINE; man->flags &= ~MDOC_NEWLINE; @@ -1020,7 +1045,7 @@ roff_addtbl(struct roff_man *man, int line, struct tbl struct roff_node *n; struct tbl_span *span; - if (man->macroset == MACROSET_MAN) + if (man->meta.macroset == MACROSET_MAN) man_breakscope(man, ROFF_TS); while ((span = tbl_span(tbl)) != NULL) { n = roff_node_alloc(man, line, 0, ROFFT_TBL, TOKEN_NONE); @@ -1064,8 +1089,8 @@ roff_node_unlink(struct roff_man *man, struct roff_nod man->next = ROFF_NEXT_SIBLING; } } - if (man->first == n) - man->first = NULL; + if (man->meta.first == n) + man->meta.first = NULL; } void @@ -1086,6 +1111,7 @@ roff_node_free(struct roff_node *n) free(n->norm); eqn_box_free(n->eqn); free(n->string); + free(n->tag); free(n); } @@ -1099,13 +1125,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; @@ -1151,419 +1236,364 @@ deroff(char **dest, const struct roff_node *n) /* --- main functions of the roff parser ---------------------------------- */ /* - * In the current line, expand escape sequences that produce parsable - * input text. Also check the syntax of the remaining escape sequences, - * which typically produce output glyphs or change formatter state. + * Save comments preceding the title macro, for example in order to + * preserve Copyright and license headers in HTML output, + * provide diagnostics about RCS ids and trailing whitespace in comments, + * then discard comments including preceding whitespace. + * This function also handles input line continuation. */ static int -roff_expand(struct roff *r, struct buf *buf, int ln, int pos, char newesc) +roff_parse_comment(struct roff *r, struct buf *buf, int ln, int pos, char ec) { - 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 */ + const char *cp; /* for RCS id parsing */ 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 */ - char *nbuf; /* new buffer to copy buf->buf to */ - size_t maxl; /* expected length of the escape name */ - size_t naml; /* actual length of the escape name */ - size_t asz; /* length of the replacement */ - size_t rsz; /* length of the rest of the string */ - int inaml; /* length returned from mandoc_escape() */ - int expand_count; /* to avoid infinite loops */ - 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; + for (start = stesc = buf->buf + pos;; stesc++) { + /* + * XXX Ugly hack: Remove the newline character that + * mparse_buf_r() appended to mark the end of input + * if it is not preceded by an escape character. + */ + if (stesc[0] == '\n') { + assert(stesc[1] == '\0'); + stesc[0] = '\0'; } - 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. */ + /* The line ends without continuation or comment. */ + if (stesc[0] == '\0') + return ROFF_CONT; - 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); + /* Unescaped byte: skip it. */ + if (stesc[0] != ec) + continue; /* - * Save comments preceding the title macro - * in the syntax tree. + * XXX Ugly hack: Do not attempt to append another line + * if the function mparse_buf_r() appended a newline + * character to indicate the end of input. */ - - if (newesc != ASCII_ESC && r->format == 0) { - 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; + if (stesc[1] == '\n') { + assert(stesc[2] == '\0'); + stesc[0] = '\0'; + return ROFF_CONT; } - /* Line continuation with comment. */ - - if (stesc[1] == '#') { - *stesc = '\0'; + /* + * An escape character at the end of an input line + * requests line continuation. + */ + if (stesc[1] == '\0') { + stesc[0] = '\0'; return ROFF_IGN | ROFF_APPEND; } - /* Discard normal comments. */ + /* Found a comment: process it. */ + if (stesc[1] == '"' || stesc[1] == '#') + break; - while (stesc > start && stesc[-1] == ' ' && - (stesc == start + 1 || stesc[-2] != '\\')) - stesc--; - *stesc = '\0'; - break; + /* Escaped escape character: skip them both. */ + if (stesc[1] == ec) + stesc++; } - if (stesc == start) - return ROFF_CONT; - stesc--; - /* Notice the end of the input. */ + /* Look for an RCS id in the comment. */ - if (*stesc == '\n') { - *stesc-- = '\0'; - done = 1; + 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; + } - expand_count = 0; - while (stesc >= start) { - if (*stesc != newesc) { + /* Warn about trailing whitespace at the end of the comment. */ - /* - * If we have a non-standard escape character, - * escape literal backslashes because all - * processing in subsequent functions uses - * the standard escaping rules. - */ + 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); - if (newesc != ASCII_ESC && *stesc == '\\') { - *stesc = '\0'; - buf->sz = mandoc_asprintf(&nbuf, "%s\\e%s", - buf->buf, stesc + 1) + 1; - start = nbuf + pos; - stesc = nbuf + (stesc - buf->buf); - free(buf->buf); - buf->buf = nbuf; - } + /* Save comments preceding the title macro in the syntax tree. */ - /* Search backwards for the next escape. */ + 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; + } - stesc--; - continue; - } + /* The comment requests line continuation. */ - /* If it is escaped, skip it. */ + if (stesc[1] == '#') { + *stesc = '\0'; + return ROFF_IGN | ROFF_APPEND; + } - for (cp = stesc - 1; cp >= start; cp--) - if (*cp != r->escape) - break; + /* Discard the comment including preceding whitespace. */ - if ((stesc - cp) % 2 == 0) { - while (stesc > cp) - *stesc-- = '\\'; - continue; - } else if (stesc[1] != '\0') { - *stesc = '\\'; - } else { - *stesc-- = '\0'; - if (done) - continue; - else - return ROFF_IGN | ROFF_APPEND; - } + while (stesc > start && stesc[-1] == ' ' && + (stesc == start + 1 || stesc[-2] != '\\')) + stesc--; + *stesc = '\0'; + return ROFF_CONT; +} - /* Decide whether to expand or to check only. */ +/* + * In the current line, expand escape sequences that produce parsable + * input text. Also check the syntax of the remaining escape sequences, + * which typically produce output glyphs or change formatter state. + */ +static int +roff_expand(struct roff *r, struct buf *buf, int ln, int pos, char ec) +{ + char ubuf[24]; /* buffer to print a number */ + struct mctx *ctx; /* current macro call context */ + const char *res; /* the string to be pasted */ + const char *src; /* source for copying */ + char *dst; /* destination for copying */ + int iesc; /* index of leading escape char */ + int inam; /* index of the escape name */ + int iarg; /* index beginning the argument */ + int iendarg; /* index right after the argument */ + int iend; /* index right after the sequence */ + int isrc, idst; /* to reduce \\ and \. in names */ + int deftype; /* type of definition to paste */ + int argi; /* macro argument index */ + int quote_args; /* true for \\$@, false for \\$* */ + int asz; /* length of the replacement */ + int rsz; /* length of the rest of the string */ + int npos; /* position in numeric expression */ + int expand_count; /* to avoid infinite loops */ - term = '\0'; - cp = stesc + 1; - if (*cp == 'E') - cp++; - esct = cp; - switch (*esct) { - case '*': - case '$': - res = NULL; - break; - case 'B': - case 'w': - term = cp[1]; - /* FALLTHROUGH */ - case 'n': - sign = cp[1]; - if (sign == '+' || sign == '-') - cp++; - res = ubuf; - break; - default: - err = MANDOCERR_OK; - switch(mandoc_escape(&cp, &stnam, &inaml)) { - case ESCAPE_SPECIAL: - if (mchars_spec2cp(stnam, inaml) >= 0) - break; - /* FALLTHROUGH */ - case ESCAPE_ERROR: - err = MANDOCERR_ESC_BAD; - break; - case ESCAPE_UNDEF: - err = MANDOCERR_ESC_UNDEF; - break; - case ESCAPE_UNSUPP: - err = MANDOCERR_ESC_UNSUPP; - break; - default: - break; + expand_count = 0; + while (buf->buf[pos] != '\0') { + + /* + * Skip plain ASCII characters. + * If we have a non-standard escape character, + * escape literal backslashes because all processing in + * subsequent functions uses the standard escaping rules. + */ + + if (buf->buf[pos] != ec) { + if (ec != ASCII_ESC && buf->buf[pos] == '\\') { + roff_expand_patch(buf, pos, "\\e", pos + 1); + pos++; } - if (err != MANDOCERR_OK) - mandoc_msg(err, ln, (int)(stesc - buf->buf), - "%.*s", (int)(cp - stesc), stesc); - stesc--; + pos++; continue; } - if (EXPAND_LIMIT < ++expand_count) { - mandoc_msg(MANDOCERR_ROFFLOOP, - ln, (int)(stesc - buf->buf), NULL); - return ROFF_IGN; - } - /* - * The third character decides the length - * of the name of the string or register. - * Save a pointer to the name. + * Parse escape sequences, + * issue diagnostic messages when appropriate, + * and skip sequences that do not need expansion. + * If we have a non-standard escape character, translate + * it to backslashes and translate backslashes to \e. */ - 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; + if (roff_escape(buf->buf, ln, pos, &iesc, &inam, + &iarg, &iendarg, &iend) != ESCAPE_EXPAND) { + while (pos < iend) { + if (buf->buf[pos] == ec) { + buf->buf[pos] = '\\'; + if (pos + 1 < iend) + pos++; + } else if (buf->buf[pos] == '\\') { + roff_expand_patch(buf, + pos, "\\e", pos + 1); + pos++; + iend++; + } + pos++; } - } else { - cp += 2; - maxl = 0; + continue; } - stnam = cp; - /* Advance to the end of the name. */ + /* Reduce \\ and \. in names. */ - naml = 0; - arg_complete = 1; - while (maxl == 0 || naml < maxl) { - if (*cp == '\0') { - mandoc_msg(MANDOCERR_ESC_BAD, ln, - (int)(stesc - buf->buf), "%s", stesc); - arg_complete = 0; - break; + if (buf->buf[inam] == '*' || buf->buf[inam] == 'n') { + isrc = idst = iarg; + while (isrc < iendarg) { + if (isrc + 1 < iendarg && + buf->buf[isrc] == '\\' && + (buf->buf[isrc + 1] == '\\' || + buf->buf[isrc + 1] == '.')) + isrc++; + buf->buf[idst++] = buf->buf[isrc++]; } - if (maxl == 0 && *cp == term) { - cp++; - break; - } - if (*cp++ != '\\' || *esct != 'w') { - naml++; - continue; - } - switch (mandoc_escape(&cp, NULL, NULL)) { - case ESCAPE_SPECIAL: - case ESCAPE_UNICODE: - case ESCAPE_NUMBERED: - case ESCAPE_UNDEF: - case ESCAPE_OVERSTRIKE: - naml++; - break; - default: - break; - } + iendarg -= isrc - idst; } - /* - * Retrieve the replacement string; if it is - * undefined, resume searching for escapes. - */ + /* Handle expansion. */ - switch (*esct) { + res = NULL; + switch (buf->buf[inam]) { case '*': - if (arg_complete) { - deftype = ROFFDEF_USER | ROFFDEF_PRE; - res = roff_getstrn(r, stnam, naml, &deftype); + if (iendarg == iarg) + break; + deftype = ROFFDEF_USER | ROFFDEF_PRE; + if ((res = roff_getstrn(r, buf->buf + iarg, + iendarg - iarg, &deftype)) != NULL) + break; - /* - * If not overriden, let \*(.T - * through to the formatters. - */ + /* + * If not overriden, + * let \*(.T through to the formatters. + */ - if (res == NULL && naml == 2 && - stnam[0] == '.' && stnam[1] == 'T') { - roff_setstrn(&r->strtab, - ".T", 2, NULL, 0, 0); - stesc--; - continue; - } + if (iendarg - iarg == 2 && + buf->buf[iarg] == '.' && + buf->buf[iarg + 1] == 'T') { + roff_setstrn(&r->strtab, ".T", 2, NULL, 0, 0); + pos = iend; + continue; } + + mandoc_msg(MANDOCERR_STR_UNDEF, ln, iesc, + "%.*s", iendarg - iarg, buf->buf + iarg); break; + case '$': if (r->mstackpos < 0) { - mandoc_msg(MANDOCERR_ARG_UNDEF, ln, - (int)(stesc - buf->buf), "%.3s", stesc); + mandoc_msg(MANDOCERR_ARG_UNDEF, ln, iesc, + "%.*s", iend - iesc, buf->buf + iesc); break; } ctx = r->mstack + r->mstackpos; - npos = esct[1] - '1'; - if (npos >= 0 && npos <= 8) { - res = npos < ctx->argc ? - ctx->argv[npos] : ""; + argi = buf->buf[iarg] - '1'; + if (argi >= 0 && argi <= 8) { + if (argi < ctx->argc) + res = ctx->argv[argi]; break; } - if (esct[1] == '*') + if (buf->buf[iarg] == '*') quote_args = 0; - else if (esct[1] == '@') + else if (buf->buf[iarg] == '@') quote_args = 1; else { - mandoc_msg(MANDOCERR_ARG_NONUM, ln, - (int)(stesc - buf->buf), "%.3s", stesc); + mandoc_msg(MANDOCERR_ARG_NONUM, ln, iesc, + "%.*s", iend - iesc, buf->buf + iesc); break; } asz = 0; - for (npos = 0; npos < ctx->argc; npos++) { - if (npos) + for (argi = 0; argi < ctx->argc; argi++) { + if (argi) asz++; /* blank */ if (quote_args) asz += 2; /* quotes */ - asz += strlen(ctx->argv[npos]); + asz += strlen(ctx->argv[argi]); } - if (asz != 3) { - rsz = buf->sz - (stesc - buf->buf) - 3; - if (asz < 3) - memmove(stesc + asz, stesc + 3, rsz); - buf->sz += asz - 3; - nbuf = mandoc_realloc(buf->buf, buf->sz); - start = nbuf + pos; - stesc = nbuf + (stesc - buf->buf); - buf->buf = nbuf; - if (asz > 3) - memmove(stesc + asz, stesc + 3, rsz); + if (asz != iend - iesc) { + rsz = buf->sz - iend; + if (asz < iend - iesc) + memmove(buf->buf + iesc + asz, + buf->buf + iend, rsz); + buf->sz = iesc + asz + rsz; + buf->buf = mandoc_realloc(buf->buf, buf->sz); + if (asz > iend - iesc) + memmove(buf->buf + iesc + asz, + buf->buf + iend, rsz); } - for (npos = 0; npos < ctx->argc; npos++) { - if (npos) - *stesc++ = ' '; + dst = buf->buf + iesc; + for (argi = 0; argi < ctx->argc; argi++) { + if (argi) + *dst++ = ' '; if (quote_args) - *stesc++ = '"'; - cp = ctx->argv[npos]; - while (*cp != '\0') - *stesc++ = *cp++; + *dst++ = '"'; + src = ctx->argv[argi]; + while (*src != '\0') + *dst++ = *src++; if (quote_args) - *stesc++ = '"'; + *dst++ = '"'; } continue; + case 'A': + ubuf[0] = iendarg > iarg ? '1' : '0'; + ubuf[1] = '\0'; + res = ubuf; + 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[0] = iendarg > iarg && iend > iendarg && + roff_evalnum(r, ln, buf->buf + iarg, &npos, + NULL, ROFFNUM_SCALE) && + npos == iendarg - iarg ? '1' : '0'; ubuf[1] = '\0'; + res = ubuf; break; + case 'V': + mandoc_msg(MANDOCERR_UNSUPP, ln, iesc, + "%.*s", iend - iesc, buf->buf + iesc); + roff_expand_patch(buf, iendarg, "}", iend); + roff_expand_patch(buf, iesc, "${", iarg); + continue; + case 'g': + break; case 'n': - if (arg_complete) + if (iendarg > iarg) (void)snprintf(ubuf, sizeof(ubuf), "%d", - roff_getregn(r, stnam, naml, sign)); + roff_getregn(r, buf->buf + iarg, + iendarg - iarg, buf->buf[inam + 1])); else ubuf[0] = '\0'; + res = ubuf; break; case 'w': - /* use even incomplete args */ - (void)snprintf(ubuf, sizeof(ubuf), "%d", - 24 * (int)naml); + (void)snprintf(ubuf, sizeof(ubuf), + "%d", (iendarg - iarg) * 24); + res = ubuf; break; + default: + break; } - - if (res == NULL) { - if (*esct == '*') - mandoc_msg(MANDOCERR_STR_UNDEF, - ln, (int)(stesc - buf->buf), - "%.*s", (int)naml, stnam); + if (res == NULL) res = ""; - } else if (buf->sz + strlen(res) > SHRT_MAX) { - mandoc_msg(MANDOCERR_ROFFLOOP, - ln, (int)(stesc - buf->buf), NULL); + if (++expand_count > EXPAND_LIMIT || + buf->sz + strlen(res) > SHRT_MAX) { + mandoc_msg(MANDOCERR_ROFFLOOP, ln, iesc, 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; + roff_expand_patch(buf, iesc, res, iend); } return ROFF_CONT; } /* + * Replace the substring from the start position (inclusive) + * to end position (exclusive) with the repl(acement) string. + */ +static void +roff_expand_patch(struct buf *buf, int start, const char *repl, int end) +{ + char *nbuf; + + buf->sz = mandoc_asprintf(&nbuf, "%.*s%s%s", start, buf->buf, + repl, buf->buf + end) + 1; + free(buf->buf); + buf->buf = nbuf; +} + +/* * Parse a quoted or unquoted roff-style request or macro argument. * Return a pointer to the parsed argument, which is either the original * pointer or advanced by one byte in case the argument is quoted. @@ -1576,7 +1606,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. */ @@ -1745,7 +1775,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; @@ -1756,6 +1786,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 && @@ -1767,8 +1805,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; @@ -1801,8 +1844,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. */ @@ -1815,7 +1860,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) { @@ -1823,18 +1867,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++; @@ -1847,9 +1900,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; @@ -1861,12 +1914,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); } /* @@ -1909,8 +1962,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) @@ -1954,15 +2009,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; @@ -1970,35 +2024,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); @@ -2006,6 +2076,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) { @@ -2165,12 +2240,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) { @@ -2196,20 +2267,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 @@ -2222,12 +2290,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; @@ -2237,23 +2313,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; @@ -2265,61 +2346,59 @@ 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, spos; + 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); + spos = pos; + 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. + * Handle requests and macros if the conditional evaluated + * to true or if they are structurally required. + * The .break request is always handled specially. */ - ep = buf->buf + pos; - if (strcmp(ep, "\\}") == 0) - rr = 0; - - /* - * 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 (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 (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; +} + +/* + * 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; @@ -2527,7 +2606,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; @@ -2673,8 +2752,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 == '"') @@ -3050,7 +3136,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; @@ -3114,7 +3200,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; @@ -3182,6 +3268,7 @@ roff_Dd(ROFF_ARGS) static int roff_TE(ROFF_ARGS) { + r->man->flags &= ~ROFF_NONOFILL; if (r->tbl == NULL) { mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "TE"); return ROFF_IGN; @@ -3281,7 +3368,7 @@ roff_EQ(ROFF_ARGS) { struct roff_node *n; - if (r->man->macroset == MACROSET_MAN) + if (r->man->meta.macroset == MACROSET_MAN) man_breakscope(r->man, ROFF_EQ); n = roff_node_alloc(r->man, ln, ppos, ROFFT_EQN, TOKEN_NONE); if (ln > r->man->last->line) @@ -3326,6 +3413,7 @@ roff_TS(ROFF_ARGS) mandoc_msg(MANDOCERR_BLK_BROKEN, ln, ppos, "TS breaks TS"); tbl_end(r->tbl, 0); } + r->man->flags |= ROFF_NONOFILL; r->tbl = tbl_alloc(ppos, ln, r->last_tbl); if (r->last_tbl == NULL) r->first_tbl = r->tbl; @@ -3334,6 +3422,26 @@ roff_TS(ROFF_ARGS) } static int +roff_noarg(ROFF_ARGS) +{ + if (r->man->flags & (MAN_BLINE | MAN_ELINE)) + man_breakscope(r->man, tok); + if (tok == ROFF_brp) + tok = ROFF_br; + roff_elem_alloc(r->man, ln, ppos, tok); + if (buf->buf[pos] != '\0') + mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos, + "%s %s", roff_name[tok], buf->buf + pos); + if (tok == ROFF_nf) + r->man->flags |= ROFF_NOFILL; + else if (tok == ROFF_fi) + r->man->flags &= ~ROFF_NOFILL; + r->man->last->flags |= NODE_LINE | NODE_VALID | NODE_ENDED; + r->man->next = ROFF_NEXT_SIBLING; + return ROFF_IGN; +} + +static int roff_onearg(ROFF_ARGS) { struct roff_node *n; @@ -3427,7 +3535,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; @@ -3443,17 +3551,14 @@ 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_br(ROFF_ARGS) +roff_break(ROFF_ARGS) { - if (r->man->flags & (MAN_BLINE | MAN_ELINE)) - man_breakscope(r->man, ROFF_br); - roff_elem_alloc(r->man, ln, ppos, ROFF_br); - if (buf->buf[pos] != '\0') - mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos, - "%s %s", roff_name[tok], buf->buf + pos); - r->man->last->flags |= NODE_LINE | NODE_VALID | NODE_ENDED; - r->man->next = ROFF_NEXT_SIBLING; + mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, pos, "break"); return ROFF_IGN; } @@ -3512,7 +3617,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; @@ -3572,6 +3679,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] == ' ') @@ -3671,7 +3826,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; @@ -3712,8 +3867,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) { @@ -3728,9 +3884,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++) @@ -3779,6 +3939,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) { @@ -3787,9 +3952,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, @@ -3826,7 +3989,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; } @@ -3847,38 +4010,67 @@ roff_renamed(ROFF_ARGS) return ROFF_CONT; } +/* + * Measure the length in bytes of the roff identifier at *cpp + * and advance the pointer to the next word. + */ static size_t roff_getname(struct roff *r, char **cpp, int ln, int pos) { char *name, *cp; - size_t namesz; + int namesz, inam, iend; name = *cpp; - if ('\0' == *name) + if (*name == '\0') return 0; - /* Read until end of name and terminate it with NUL. */ - for (cp = name; 1; cp++) { - if ('\0' == *cp || ' ' == *cp) { - namesz = cp - name; + /* Advance cp to the byte after the end of the name. */ + + cp = name; + namesz = 0; + for (;;) { + if (*cp == '\0') break; + if (*cp == ' ' || *cp == '\t') { + cp++; + break; } - if ('\\' != *cp) + if (*cp != '\\') { + if (name + namesz < cp) { + name[namesz] = *cp; + *cp = ' '; + } + namesz++; + cp++; continue; - namesz = cp - name; - if ('{' == cp[1] || '}' == cp[1]) + } + if (cp[1] == '{' || cp[1] == '}') break; - cp++; - if ('\\' == *cp) - continue; - mandoc_msg(MANDOCERR_NAMESC, ln, pos, - "%.*s", (int)(cp - name + 1), name); - mandoc_escape((const char **)&cp, NULL, NULL); - break; + if (roff_escape(cp, 0, 0, NULL, &inam, + NULL, NULL, &iend) != ESCAPE_UNDEF) { + mandoc_msg(MANDOCERR_NAMESC, ln, pos, + "%.*s%.*s", namesz, name, iend, cp); + cp += iend; + break; + } + + /* + * In an identifier, \\, \., \G and so on + * are reduced to \, ., G and so on, + * vaguely similar to copy mode. + */ + + name[namesz++] = cp[inam]; + while (iend--) { + if (cp >= name + namesz) + *cp = ' '; + cp++; + } } /* Read past spaces. */ - while (' ' == *cp) + + while (*cp == ' ') cp++; *cpp = cp; @@ -4021,7 +4213,7 @@ roff_getstrn(struct roff *r, const char *name, size_t break; } } - if (r->man->macroset != MACROSET_MAN) { + if (r->man->meta.macroset != MACROSET_MAN) { for (tok = MDOC_Dd; tok < MDOC_MAX; tok++) { if (strncmp(name, roff_name[tok], len) != 0 || roff_name[tok][len] != '\0') @@ -4035,7 +4227,7 @@ roff_getstrn(struct roff *r, const char *name, size_t } } } - if (r->man->macroset != MACROSET_MDOC) { + if (r->man->meta.macroset != MACROSET_MDOC) { for (tok = MAN_TH; tok < MAN_MAX; tok++) { if (strncmp(name, roff_name[tok], len) != 0 || roff_name[tok][len] != '\0')