diff --git a/contrib/mandoc/mdoc.c b/contrib/mandoc/mdoc.c index 475a6aa0ddb3..90a079870f64 100644 --- a/contrib/mandoc/mdoc.c +++ b/contrib/mandoc/mdoc.c @@ -1,433 +1,432 @@ /* $Id: mdoc.c,v 1.275 2020/04/06 10:16:17 schwarze Exp $ */ /* * Copyright (c) 2010, 2012-2018, 2020 Ingo Schwarze * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * 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. * * Top level and utility functions of the mdoc(7) parser for mandoc(1). */ #include "config.h" #include #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "libmandoc.h" #include "roff_int.h" #include "libmdoc.h" const char *const __mdoc_argnames[MDOC_ARG_MAX] = { "split", "nosplit", "ragged", "unfilled", "literal", "file", "offset", "bullet", "dash", "hyphen", "item", "enum", "tag", "diag", "hang", "ohang", "inset", "column", "width", "compact", "std", "filled", "words", "emphasis", "symbolic", "nested", "centered" }; const char * const *mdoc_argnames = __mdoc_argnames; static int mdoc_ptext(struct roff_man *, int, char *, int); static int mdoc_pmacro(struct roff_man *, int, char *, int); /* * Main parse routine. Parses a single line -- really just hands off to * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). */ int mdoc_parseln(struct roff_man *mdoc, int ln, char *buf, int offs) { if (mdoc->last->type != ROFFT_EQN || ln > mdoc->last->line) mdoc->flags |= MDOC_NEWLINE; /* * Let the roff nS register switch SYNOPSIS mode early, * such that the parser knows at all times * whether this mode is on or off. * Note that this mode is also switched by the Sh macro. */ if (roff_getreg(mdoc->roff, "nS")) mdoc->flags |= MDOC_SYNOPSIS; else mdoc->flags &= ~MDOC_SYNOPSIS; return roff_getcontrol(mdoc->roff, buf, &offs) ? mdoc_pmacro(mdoc, ln, buf, offs) : mdoc_ptext(mdoc, ln, buf, offs); } void mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok) { struct roff_node *p; p = roff_node_alloc(mdoc, line, pos, ROFFT_TAIL, tok); roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_CHILD; } struct roff_node * mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok, struct roff_node *body) { struct roff_node *p; body->flags |= NODE_ENDED; body->parent->flags |= NODE_ENDED; p = roff_node_alloc(mdoc, line, pos, ROFFT_BODY, tok); p->body = body; p->norm = body->norm; p->end = ENDBODY_SPACE; roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_SIBLING; return p; } struct roff_node * mdoc_block_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok, struct mdoc_arg *args) { struct roff_node *p; p = roff_node_alloc(mdoc, line, pos, ROFFT_BLOCK, tok); p->args = args; if (p->args) (args->refcnt)++; switch (tok) { case MDOC_Bd: case MDOC_Bf: case MDOC_Bl: case MDOC_En: case MDOC_Rs: p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); break; default: break; } roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_CHILD; return p; } void mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok, struct mdoc_arg *args) { struct roff_node *p; p = roff_node_alloc(mdoc, line, pos, ROFFT_ELEM, tok); p->args = args; if (p->args) (args->refcnt)++; switch (tok) { case MDOC_An: p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); break; default: break; } roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_CHILD; } /* * Parse free-form text, that is, a line that does not begin with the * control character. */ static int mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs) { struct roff_node *n; - const char *cp, *sp; - char *c, *ws, *end; + char *c, *ws, *end, *cp, *sp; n = mdoc->last; /* * If a column list contains plain text, assume an implicit item * macro. This can happen one or more times at the beginning * of such a list, intermixed with non-It mdoc macros and with * nodes generated on the roff level, for example by tbl. */ if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || (n->parent != NULL && n->parent->tok == MDOC_Bl && n->parent->norm->Bl.type == LIST_column)) { mdoc->flags |= MDOC_FREECOL; (*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It, line, offs, &offs, buf); return 1; } /* * Search for the beginning of unescaped trailing whitespace (ws) * and for the first character not to be output (end). */ /* FIXME: replace with strcspn(). */ ws = NULL; for (c = end = buf + offs; *c; c++) { switch (*c) { case ' ': if (NULL == ws) ws = c; continue; case '\t': /* * Always warn about trailing tabs, * even outside literal context, * where they should be put on the next line. */ if (NULL == ws) ws = c; /* * Strip trailing tabs in literal context only; * outside, they affect the next line. */ if (mdoc->flags & ROFF_NOFILL) continue; break; case '\\': /* Skip the escaped character, too, if any. */ if (c[1]) c++; /* FALLTHROUGH */ default: ws = NULL; break; } end = c + 1; } *end = '\0'; if (ws) mandoc_msg(MANDOCERR_SPACE_EOL, line, (int)(ws - buf), NULL); /* * Blank lines are allowed in no-fill mode * and cancel preceding \c, * but add a single vertical space elsewhere. */ if (buf[offs] == '\0' && (mdoc->flags & ROFF_NOFILL) == 0) { switch (mdoc->last->type) { case ROFFT_TEXT: sp = mdoc->last->string; cp = end = strchr(sp, '\0') - 2; if (cp < sp || cp[0] != '\\' || cp[1] != 'c') break; while (cp > sp && cp[-1] == '\\') cp--; if ((end - cp) % 2) break; *end = '\0'; return 1; default: break; } mandoc_msg(MANDOCERR_FI_BLANK, line, (int)(c - buf), NULL); roff_elem_alloc(mdoc, line, offs, ROFF_sp); mdoc->last->flags |= NODE_VALID | NODE_ENDED; mdoc->next = ROFF_NEXT_SIBLING; return 1; } roff_word_alloc(mdoc, line, offs, buf+offs); if (mdoc->flags & ROFF_NOFILL) return 1; /* * End-of-sentence check. If the last character is an unescaped * EOS character, then flag the node as being the end of a * sentence. The front-end will know how to interpret this. */ assert(buf < end); if (mandoc_eos(buf+offs, (size_t)(end-buf-offs))) mdoc->last->flags |= NODE_EOS; for (c = buf + offs; c != NULL; c = strchr(c + 1, '.')) { if (c - buf < offs + 2) continue; if (end - c < 3) break; if (c[1] != ' ' || isalnum((unsigned char)c[-2]) == 0 || isalnum((unsigned char)c[-1]) == 0 || (c[-2] == 'n' && c[-1] == 'c') || (c[-2] == 'v' && c[-1] == 's')) continue; c += 2; if (*c == ' ') c++; if (*c == ' ') c++; if (isupper((unsigned char)(*c))) mandoc_msg(MANDOCERR_EOS, line, (int)(c - buf), NULL); } return 1; } /* * Parse a macro line, that is, a line beginning with the control * character. */ static int mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs) { struct roff_node *n; const char *cp; size_t sz; enum roff_tok tok; int sv; /* Determine the line macro. */ sv = offs; tok = TOKEN_NONE; for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++) offs++; if (sz == 2 || sz == 3) tok = roffhash_find(mdoc->mdocmac, buf + sv, sz); if (tok == TOKEN_NONE) { mandoc_msg(MANDOCERR_MACRO, ln, sv, "%s", buf + sv - 1); return 1; } /* Skip a leading escape sequence or tab. */ switch (buf[offs]) { case '\\': cp = buf + offs + 1; mandoc_escape(&cp, NULL, NULL); offs = cp - buf; break; case '\t': offs++; break; default: break; } /* Jump to the next non-whitespace word. */ while (buf[offs] == ' ') offs++; /* * Trailing whitespace. Note that tabs are allowed to be passed * into the parser as "text", so we only warn about spaces here. */ if ('\0' == buf[offs] && ' ' == buf[offs - 1]) mandoc_msg(MANDOCERR_SPACE_EOL, ln, offs - 1, NULL); /* * If an initial or transparent macro or a list invocation, * divert directly into macro processing. */ n = mdoc->last; if (n == NULL || tok == MDOC_It || tok == MDOC_El || roff_tok_transparent(tok)) { (*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf); return 1; } /* * If a column list contains a non-It macro, assume an implicit * item macro. This can happen one or more times at the * beginning of such a list, intermixed with text lines and * with nodes generated on the roff level, for example by tbl. */ if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || (n->parent != NULL && n->parent->tok == MDOC_Bl && n->parent->norm->Bl.type == LIST_column)) { mdoc->flags |= MDOC_FREECOL; (*mdoc_macro(MDOC_It)->fp)(mdoc, MDOC_It, ln, sv, &sv, buf); return 1; } /* Normal processing of a macro. */ (*mdoc_macro(tok)->fp)(mdoc, tok, ln, sv, &offs, buf); /* In quick mode (for mandocdb), abort after the NAME section. */ if (mdoc->quick && MDOC_Sh == tok && SEC_NAME != mdoc->last->sec) return 2; return 1; } enum mdelim mdoc_isdelim(const char *p) { if ('\0' == p[0]) return DELIM_NONE; if ('\0' == p[1]) switch (p[0]) { case '(': case '[': return DELIM_OPEN; case '|': return DELIM_MIDDLE; case '.': case ',': case ';': case ':': case '?': case '!': case ')': case ']': return DELIM_CLOSE; default: return DELIM_NONE; } if ('\\' != p[0]) return DELIM_NONE; if (0 == strcmp(p + 1, ".")) return DELIM_CLOSE; if (0 == strcmp(p + 1, "fR|\\fP")) return DELIM_MIDDLE; return DELIM_NONE; } diff --git a/contrib/mandoc/out.c b/contrib/mandoc/out.c index 21c282b2141b..082e7e61e8af 100644 --- a/contrib/mandoc/out.c +++ b/contrib/mandoc/out.c @@ -1,570 +1,570 @@ /* $Id: out.c,v 1.87 2025/07/16 14:33:08 schwarze Exp $ */ /* * Copyright (c) 2011, 2014, 2015, 2017, 2018, 2019, 2021, 2025 * Ingo Schwarze * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * 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. */ #include "config.h" #include #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc.h" #include "tbl.h" #include "out.h" struct tbl_colgroup { struct tbl_colgroup *next; size_t wanted; int startcol; int endcol; }; static size_t tblcalc_data(struct rofftbl *, struct roffcol *, const struct tbl_opts *, const struct tbl_dat *, size_t); static size_t tblcalc_literal(struct rofftbl *, struct roffcol *, const struct tbl_dat *, size_t); static size_t tblcalc_number(struct rofftbl *, struct roffcol *, const struct tbl_opts *, const struct tbl_dat *); /* * Parse the *src string and store a scaling unit into *dst. * If the string doesn't specify the unit, use the default. * If no default is specified, fail. * Return a pointer to the byte after the last byte used, * or NULL on total failure. */ const char * a2roffsu(const char *src, struct roffsu *dst, enum roffscale def) { char *endptr; dst->unit = def == SCALE_MAX ? SCALE_BU : def; dst->scale = strtod(src, &endptr); if (endptr == src) return NULL; switch (*endptr++) { case 'c': dst->unit = SCALE_CM; break; case 'i': dst->unit = SCALE_IN; break; case 'f': dst->unit = SCALE_FS; break; case 'M': dst->unit = SCALE_MM; break; case 'm': dst->unit = SCALE_EM; break; case 'n': dst->unit = SCALE_EN; break; case 'P': dst->unit = SCALE_PC; break; case 'p': dst->unit = SCALE_PT; break; case 'u': dst->unit = SCALE_BU; break; case 'v': dst->unit = SCALE_VS; break; default: endptr--; if (SCALE_MAX == def) return NULL; dst->unit = def; break; } return endptr; } /* * Calculate the abstract widths and decimal positions of columns in a * table. This routine allocates the columns structures then runs over * all rows and cells in the table. The function pointers in "tbl" are * used for the actual width calculations. */ void tblcalc(struct rofftbl *tbl, const struct tbl_span *sp_first, size_t offset, size_t rmargin) { const struct tbl_opts *opts; const struct tbl_span *sp; const struct tbl_dat *dp; struct roffcol *col; struct tbl_colgroup *first_group, **gp, *g; /* Widths in basic units. */ size_t *colwidth; /* Widths of all columns. */ size_t min1; /* Width of the narrowest column. */ size_t min2; /* Width of the second narrowest column. */ size_t wanted; /* For any of the narrowest columns. */ size_t xwidth; /* Total width of columns not to expand. */ size_t ewidth; /* Width of widest column to equalize. */ size_t width; /* Width of the data in basic units. */ size_t enw; /* Width of one EN unit. */ int icol; /* Column number, starting at zero. */ int maxcol; /* Number of last column. */ int necol; /* Number of columns to equalize. */ int nxcol; /* Number of columns to expand. */ int done; /* Boolean: this group is wide enough. */ int quirkcol; /* * Allocate the master column specifiers. These will hold the * widths and decimal positions for all cells in the column. It * must be freed and nullified by the caller. */ assert(tbl->cols == NULL); tbl->cols = mandoc_calloc((size_t)sp_first->opts->cols, sizeof(struct roffcol)); opts = sp_first->opts; maxcol = -1; first_group = NULL; enw = (*tbl->len)(1, tbl->arg); for (sp = sp_first; sp != NULL; sp = sp->next) { if (sp->pos != TBL_SPAN_DATA) continue; /* * Account for the data cells in the layout, matching it * to data cells in the data section. */ for (dp = sp->first; dp != NULL; dp = dp->next) { icol = dp->layout->col; while (maxcol < icol + dp->hspans) tbl->cols[++maxcol].spacing = SIZE_MAX; col = tbl->cols + icol; col->flags |= dp->layout->flags; if (dp->layout->flags & TBL_CELL_WIGN) continue; /* Handle explicit width specifications. */ if (col->width < dp->layout->width) col->width = dp->layout->width; if (dp->layout->spacing != SIZE_MAX && (col->spacing == SIZE_MAX || col->spacing < dp->layout->spacing)) col->spacing = dp->layout->spacing; /* * Calculate an automatic width. * Except for spanning cells, apply it. */ width = tblcalc_data(tbl, dp->hspans == 0 ? col : NULL, opts, dp, dp->block == 0 ? 0 : dp->layout->width ? dp->layout->width : rmargin ? (rmargin / enw + sp->opts->cols / 2) / (sp->opts->cols + 1) * enw : 0); if (dp->hspans == 0) continue; /* * Build a singly linked list * of all groups of columns joined by spans, * recording the minimum width for each group. */ gp = &first_group; while (*gp != NULL && ((*gp)->startcol != icol || (*gp)->endcol != icol + dp->hspans)) gp = &(*gp)->next; if (*gp == NULL) { g = mandoc_malloc(sizeof(*g)); g->next = *gp; g->wanted = width; g->startcol = icol; g->endcol = icol + dp->hspans; *gp = g; } else if ((*gp)->wanted < width) (*gp)->wanted = width; } } /* * The minimum width of columns explicitly specified * in the layout is 1n. */ if (maxcol < sp_first->opts->cols - 1) maxcol = sp_first->opts->cols - 1; for (icol = 0; icol <= maxcol; icol++) { col = tbl->cols + icol; if (col->width < enw) col->width = enw; /* * Column spacings are needed for span width * calculations, so set the default values now. */ if (col->spacing == SIZE_MAX || icol == maxcol) col->spacing = 3; } /* * Replace the minimum widths with the missing widths, * and dismiss groups that are already wide enough. */ gp = &first_group; while ((g = *gp) != NULL) { done = 0; for (icol = g->startcol; icol <= g->endcol; icol++) { width = tbl->cols[icol].width; if (icol < g->endcol) width += (*tbl->len)(tbl->cols[icol].spacing, tbl->arg); if (g->wanted <= width) { done = 1; break; } else g->wanted -= width; } if (done) { *gp = g->next; free(g); } else gp = &g->next; } colwidth = mandoc_reallocarray(NULL, maxcol + 1, sizeof(*colwidth)); while (first_group != NULL) { /* * Rebuild the array of the widths of all columns * participating in spans that require expansion. */ for (icol = 0; icol <= maxcol; icol++) colwidth[icol] = SIZE_MAX; for (g = first_group; g != NULL; g = g->next) for (icol = g->startcol; icol <= g->endcol; icol++) colwidth[icol] = tbl->cols[icol].width; /* * Find the smallest and second smallest column width * among the columns which may need expamsion. */ min1 = min2 = SIZE_MAX; for (icol = 0; icol <= maxcol; icol++) { width = colwidth[icol]; if (min1 > width) { min2 = min1; min1 = width; } else if (min1 < width && min2 > width) min2 = width; } /* * Find the minimum wanted width * for any one of the narrowest columns, * and mark the columns wanting that width. */ wanted = min2; for (g = first_group; g != NULL; g = g->next) { necol = 0; for (icol = g->startcol; icol <= g->endcol; icol++) if (colwidth[icol] == min1) necol++; if (necol == 0) continue; width = min1 + (g->wanted - 1) / necol + 1; if (width > min2) width = min2; if (wanted > width) wanted = width; } /* Record the effect of the widening. */ gp = &first_group; while ((g = *gp) != NULL) { done = 0; for (icol = g->startcol; icol <= g->endcol; icol++) { if (colwidth[icol] != min1) continue; if (g->wanted <= wanted - min1) { tbl->cols[icol].width += g->wanted; done = 1; break; } tbl->cols[icol].width = wanted; g->wanted -= wanted - min1; } if (done) { *gp = g->next; free(g); } else gp = &g->next; } } free(colwidth); /* * Align numbers with text. * Count columns to equalize and columns to maximize. * Find maximum width of the columns to equalize. * Find total width of the columns *not* to maximize. */ necol = nxcol = 0; ewidth = xwidth = 0; for (icol = 0; icol <= maxcol; icol++) { col = tbl->cols + icol; if (col->width > col->nwidth) col->decimal += (col->width - col->nwidth) / 2; if (col->flags & TBL_CELL_EQUAL) { necol++; if (ewidth < col->width) ewidth = col->width; } if (col->flags & TBL_CELL_WMAX) nxcol++; else xwidth += col->width; } /* * Equalize columns, if requested for any of them. * Update total width of the columns not to maximize. */ if (necol) { for (icol = 0; icol <= maxcol; icol++) { col = tbl->cols + icol; if ( ! (col->flags & TBL_CELL_EQUAL)) continue; if (col->width == ewidth) continue; if (nxcol && rmargin) xwidth += ewidth - col->width; col->width = ewidth; } } /* * If there are any columns to maximize, find the total * available width, deducting 3n margins between columns. * Distribute the available width evenly. */ if (nxcol && rmargin) { xwidth += (*tbl->len)(3 * maxcol + (opts->opts & (TBL_OPT_BOX | TBL_OPT_DBOX) ? 2 : !!opts->lvert + !!opts->rvert), tbl->arg); if (rmargin <= offset + xwidth) return; xwidth = rmargin - offset - xwidth; /* * Emulate a bug in GNU tbl width calculation that * manifests itself for large numbers of x-columns. * Emulating it for 5 x-columns gives identical * behaviour for up to 6 x-columns. */ if (nxcol == 5) { quirkcol = xwidth / enw % nxcol + 2; if (quirkcol != 3 && quirkcol != 4) quirkcol = -1; } else quirkcol = -1; necol = 0; ewidth = 0; for (icol = 0; icol <= maxcol; icol++) { col = tbl->cols + icol; if ( ! (col->flags & TBL_CELL_WMAX)) continue; col->width = (double)xwidth * ++necol / nxcol - ewidth + 0.4995; if (necol == quirkcol) col->width -= enw; ewidth += col->width; } } } static size_t tblcalc_data(struct rofftbl *tbl, struct roffcol *col, const struct tbl_opts *opts, const struct tbl_dat *dp, size_t mw) { size_t sz; /* Branch down into data sub-types. */ switch (dp->layout->pos) { case TBL_CELL_HORIZ: case TBL_CELL_DHORIZ: sz = (*tbl->len)(1, tbl->arg); if (col != NULL && col->width < sz) col->width = sz; return sz; case TBL_CELL_LONG: case TBL_CELL_CENTRE: case TBL_CELL_LEFT: case TBL_CELL_RIGHT: return tblcalc_literal(tbl, col, dp, mw); case TBL_CELL_NUMBER: return tblcalc_number(tbl, col, opts, dp); case TBL_CELL_DOWN: return 0; default: abort(); } } static size_t tblcalc_literal(struct rofftbl *tbl, struct roffcol *col, const struct tbl_dat *dp, size_t mw) { - const char *str; /* Beginning of the first line. */ - const char *beg; /* Beginning of the current line. */ + char *str; /* Beginning of the first line. */ + char *beg; /* Beginning of the current line. */ char *end; /* End of the current line. */ /* Widths in basic units. */ size_t lsz; /* Of the current line. */ size_t wsz; /* Of the current word. */ size_t msz; /* Of the longest line. */ size_t enw; /* Of one EN unit. */ if (dp->string == NULL || *dp->string == '\0') return 0; str = mw ? mandoc_strdup(dp->string) : dp->string; msz = lsz = 0; for (beg = str; beg != NULL && *beg != '\0'; beg = end) { end = mw ? strchr(beg, ' ') : NULL; if (end != NULL) { *end++ = '\0'; while (*end == ' ') end++; } wsz = (*tbl->slen)(beg, tbl->arg); enw = (*tbl->len)(1, tbl->arg); if (mw && lsz && lsz + enw + wsz <= mw) lsz += enw + wsz; else lsz = wsz; if (msz < lsz) msz = lsz; } if (mw) free((void *)str); if (col != NULL && col->width < msz) col->width = msz; return msz; } static size_t tblcalc_number(struct rofftbl *tbl, struct roffcol *col, const struct tbl_opts *opts, const struct tbl_dat *dp) { const char *cp, *lastdigit, *lastpoint; size_t totsz; /* Total width of the number in basic units. */ size_t intsz; /* Width of the integer part in basic units. */ char buf[2]; if (dp->string == NULL || *dp->string == '\0') return 0; totsz = (*tbl->slen)(dp->string, tbl->arg); if (col == NULL) return totsz; /* * Find the last digit and * the last decimal point that is adjacent to a digit. * The alignment indicator "\&" overrides everything. */ lastdigit = lastpoint = NULL; for (cp = dp->string; cp[0] != '\0'; cp++) { if (cp[0] == '\\' && cp[1] == '&') { lastdigit = lastpoint = cp; break; } else if (cp[0] == opts->decimal && (isdigit((unsigned char)cp[1]) || (cp > dp->string && isdigit((unsigned char)cp[-1])))) lastpoint = cp; else if (isdigit((unsigned char)cp[0])) lastdigit = cp; } /* Not a number, treat as a literal string. */ if (lastdigit == NULL) { if (col != NULL && col->width < totsz) col->width = totsz; return totsz; } /* Measure the width of the integer part. */ if (lastpoint == NULL) lastpoint = lastdigit + 1; intsz = 0; buf[1] = '\0'; for (cp = dp->string; cp < lastpoint; cp++) { buf[0] = cp[0]; intsz += (*tbl->slen)(buf, tbl->arg); } /* * If this number has more integer digits than all numbers * seen on earlier lines, shift them all to the right. * If it has fewer, shift this number to the right. */ if (intsz > col->decimal) { col->nwidth += intsz - col->decimal; col->decimal = intsz; } else totsz += col->decimal - intsz; /* Update the maximum total width seen so far. */ if (totsz > col->nwidth) col->nwidth = totsz; if (col->nwidth > col->width) col->width = col->nwidth; return totsz; } diff --git a/contrib/mandoc/read.c b/contrib/mandoc/read.c index 0e8d32df5a5e..d958a25832b3 100644 --- a/contrib/mandoc/read.c +++ b/contrib/mandoc/read.c @@ -1,737 +1,739 @@ /* $Id: read.c,v 1.221 2022/05/19 14:48:56 schwarze Exp $ */ /* * Copyright (c) 2010-2020 Ingo Schwarze * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons * Copyright (c) 2010, 2012 Joerg Sonnenberger * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * 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. * * Top-level functions of the mandoc(3) parser: * Parser and input encoding selection, decompression, * handling of input bytes, characters, lines, and files, * handling of roff(7) loops and file inclusion, * and steering of the various parsers. */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mandoc_aux.h" #include "mandoc.h" #include "roff.h" #include "mdoc.h" #include "man.h" #include "mandoc_parse.h" #include "libmandoc.h" #include "roff_int.h" #include "tag.h" #define REPARSE_LIMIT 1000 struct mparse { struct roff *roff; /* roff parser (!NULL) */ struct roff_man *man; /* man parser */ struct buf *primary; /* buffer currently being parsed */ struct buf *secondary; /* copy of top level input */ struct buf *loop; /* open .while request line */ const char *os_s; /* default operating system */ int options; /* parser options */ int gzip; /* current input file is gzipped */ int filenc; /* encoding of the current file */ int reparse_count; /* finite interp. stack */ int line; /* line number in the file */ }; static void choose_parser(struct mparse *); static void free_buf_list(struct buf *); static void resize_buf(struct buf *, size_t); static int mparse_buf_r(struct mparse *, struct buf, size_t, int); static int read_whole_file(struct mparse *, int, struct buf *, int *); static void mparse_end(struct mparse *); static void resize_buf(struct buf *buf, size_t initial) { buf->sz = buf->sz > initial/2 ? 2 * buf->sz : initial; buf->buf = mandoc_realloc(buf->buf, buf->sz); } static void free_buf_list(struct buf *buf) { struct buf *tmp; while (buf != NULL) { tmp = buf; buf = tmp->next; free(tmp->buf); free(tmp); } } static void choose_parser(struct mparse *curp) { char *cp, *ep; int format; /* * If neither command line arguments -mdoc or -man select * a parser nor the roff parser found a .Dd or .TH macro * yet, look ahead in the main input buffer. */ if ((format = roff_getformat(curp->roff)) == 0) { cp = curp->primary->buf; ep = cp + curp->primary->sz; while (cp < ep) { if (*cp == '.' || *cp == '\'') { cp++; if (cp[0] == 'D' && cp[1] == 'd') { format = MPARSE_MDOC; break; } if (cp[0] == 'T' && cp[1] == 'H') { format = MPARSE_MAN; break; } } cp = memchr(cp, '\n', ep - cp); if (cp == NULL) break; cp++; } } if (format == MPARSE_MDOC) { curp->man->meta.macroset = MACROSET_MDOC; if (curp->man->mdocmac == NULL) curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX); } else { curp->man->meta.macroset = MACROSET_MAN; if (curp->man->manmac == NULL) curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX); } curp->man->meta.first->tok = TOKEN_NONE; } /* * Main parse routine for a buffer. * It assumes encoding and line numbering are already set up. * It can recurse directly (for invocations of user-defined * macros, inline equations, and input line traps) * and indirectly (for .so file inclusion). */ static int mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start) { struct buf ln; struct buf *firstln, *lastln, *thisln, *loop; char *cp; size_t pos; /* byte number in the ln buffer */ size_t spos; /* at the start of the current line parse */ int line_result, result; int of; int lnn; /* line number in the real file */ int fd; int inloop; /* Saw .while on this level. */ unsigned char c; ln.sz = 256; ln.buf = mandoc_malloc(ln.sz); ln.next = NULL; firstln = lastln = loop = NULL; lnn = curp->line; pos = 0; inloop = 0; result = ROFF_CONT; while (i < blk.sz && (blk.buf[i] != '\0' || pos != 0)) { if (start) { curp->line = lnn; curp->reparse_count = 0; if (lnn < 3 && curp->filenc & MPARSE_UTF8 && curp->filenc & MPARSE_LATIN1) curp->filenc = preconv_cue(&blk, i); } spos = pos; while (i < blk.sz && (start || blk.buf[i] != '\0')) { /* * When finding an unescaped newline character, * leave the character loop to process the line. * Skip a preceding carriage return, if any. */ if ('\r' == blk.buf[i] && i + 1 < blk.sz && '\n' == blk.buf[i + 1]) ++i; if ('\n' == blk.buf[i]) { ++i; ++lnn; break; } /* * Make sure we have space for the worst * case of 12 bytes: "\\[u10ffff]\n\0" */ if (pos + 12 > ln.sz) resize_buf(&ln, 256); /* * Encode 8-bit input. */ c = blk.buf[i]; if (c & 0x80) { if ( ! (curp->filenc && preconv_encode( &blk, &i, &ln, &pos, &curp->filenc))) { mandoc_msg(MANDOCERR_CHAR_BAD, curp->line, pos, "0x%x", c); ln.buf[pos++] = '?'; i++; } continue; } /* * Exclude control characters. */ if (c == 0x7f || (c < 0x20 && c != 0x09)) { mandoc_msg(c == 0x00 || c == 0x04 || c > 0x0a ? MANDOCERR_CHAR_BAD : MANDOCERR_CHAR_UNSUPP, curp->line, pos, "0x%x", c); i++; if (c != '\r') ln.buf[pos++] = '?'; continue; } ln.buf[pos++] = blk.buf[i++]; } ln.buf[pos] = '\0'; /* * Maintain a lookaside buffer of all lines. * parsed from this input source. */ thisln = mandoc_malloc(sizeof(*thisln)); thisln->buf = mandoc_strdup(ln.buf); thisln->sz = strlen(ln.buf) + 1; thisln->next = NULL; if (firstln == NULL) { firstln = lastln = thisln; if (curp->secondary == NULL) curp->secondary = firstln; } else { lastln->next = thisln; lastln = thisln; } /* * XXX Ugly hack to mark the end of the input, * such that the function roff_parse_comment() * doesn't attempt to append another line if the * last input line ends with an escape character. */ if (i == blk.sz || blk.buf[i] == '\0') { if (pos + 2 > ln.sz) resize_buf(&ln, 256); ln.buf[pos++] = '\n'; ln.buf[pos] = '\0'; } /* * A significant amount of complexity is contained by * the roff preprocessor. It's line-oriented but can be * expressed on one line, so we need at times to * readjust our starting point and re-run it. The roff * preprocessor can also readjust the buffers with new * data, so we pass them in wholesale. */ of = 0; rerun: line_result = roff_parseln(curp->roff, curp->line, &ln, &of, start && spos == 0 ? pos : 0); /* Process options. */ if (line_result & ROFF_APPEND) assert(line_result == (ROFF_IGN | ROFF_APPEND)); if (line_result & ROFF_USERCALL) assert((line_result & ROFF_MASK) == ROFF_REPARSE); if (line_result & ROFF_USERRET) { assert(line_result == (ROFF_IGN | ROFF_USERRET)); if (start == 0) { /* Return from the current macro. */ result = ROFF_USERRET; goto out; } } switch (line_result & ROFF_LOOPMASK) { case ROFF_IGN: break; case ROFF_WHILE: if (curp->loop != NULL) { if (loop == curp->loop) break; mandoc_msg(MANDOCERR_WHILE_NEST, curp->line, pos, NULL); } curp->loop = thisln; loop = NULL; inloop = 1; break; case ROFF_LOOPCONT: case ROFF_LOOPEXIT: if (curp->loop == NULL) { mandoc_msg(MANDOCERR_WHILE_FAIL, curp->line, pos, NULL); break; } if (inloop == 0) { mandoc_msg(MANDOCERR_WHILE_INTO, curp->line, pos, NULL); curp->loop = loop = NULL; break; } if (line_result & ROFF_LOOPCONT) loop = curp->loop; else { curp->loop = loop = NULL; inloop = 0; } break; default: abort(); } /* Process the main instruction from the roff parser. */ switch (line_result & ROFF_MASK) { case ROFF_IGN: break; case ROFF_CONT: if (curp->man->meta.macroset == MACROSET_NONE) choose_parser(curp); if ((curp->man->meta.macroset == MACROSET_MDOC ? mdoc_parseln(curp->man, curp->line, ln.buf, of) : man_parseln(curp->man, curp->line, ln.buf, of) ) == 2) goto out; break; case ROFF_RERUN: goto rerun; case ROFF_REPARSE: if (++curp->reparse_count > REPARSE_LIMIT) { /* Abort and return to the top level. */ result = ROFF_IGN; mandoc_msg(MANDOCERR_ROFFLOOP, curp->line, pos, NULL); goto out; } result = mparse_buf_r(curp, ln, of, 0); if (line_result & ROFF_USERCALL) { roff_userret(curp->roff); /* Continue normally. */ if (result & ROFF_USERRET) result = ROFF_CONT; } if (start == 0 && result != ROFF_CONT) goto out; break; case ROFF_SO: if ( ! (curp->options & MPARSE_SO) && (i >= blk.sz || blk.buf[i] == '\0')) { curp->man->meta.sodest = mandoc_strdup(ln.buf + of); goto out; } if ((fd = mparse_open(curp, ln.buf + of)) != -1) { mparse_readfd(curp, fd, ln.buf + of); close(fd); } else { mandoc_msg(MANDOCERR_SO_FAIL, curp->line, of, ".so %s: %s", ln.buf + of, strerror(errno)); ln.sz = mandoc_asprintf(&cp, ".sp\nSee the file %s.\n.sp", ln.buf + of); free(ln.buf); ln.buf = cp; of = 0; mparse_buf_r(curp, ln, of, 0); } break; default: abort(); } /* Start the next input line. */ if (loop != NULL && (line_result & ROFF_LOOPMASK) == ROFF_IGN) loop = loop->next; if (loop != NULL) { if ((line_result & ROFF_APPEND) == 0) *ln.buf = '\0'; if (ln.sz < loop->sz) resize_buf(&ln, loop->sz); (void)strlcat(ln.buf, loop->buf, ln.sz); of = 0; goto rerun; } pos = (line_result & ROFF_APPEND) ? strlen(ln.buf) : 0; } out: if (inloop) { if (result != ROFF_USERRET) mandoc_msg(MANDOCERR_WHILE_OUTOF, curp->line, pos, NULL); curp->loop = NULL; } free(ln.buf); if (firstln != curp->secondary) free_buf_list(firstln); return result; } static int read_whole_file(struct mparse *curp, int fd, struct buf *fb, int *with_mmap) { struct stat st; gzFile gz; size_t off; ssize_t ssz; int gzerrnum, retval; if (fstat(fd, &st) == -1) { mandoc_msg(MANDOCERR_FSTAT, 0, 0, "%s", strerror(errno)); return -1; } /* * If we're a regular file, try just reading in the whole entry * via mmap(). This is faster than reading it into blocks, and * since each file is only a few bytes to begin with, I'm not * concerned that this is going to tank any machines. */ if (curp->gzip == 0 && S_ISREG(st.st_mode)) { if (st.st_size > 0x7fffffff) { mandoc_msg(MANDOCERR_TOOLARGE, 0, 0, NULL); return -1; } *with_mmap = 1; fb->sz = (size_t)st.st_size; fb->buf = mmap(NULL, fb->sz, PROT_READ, MAP_SHARED, fd, 0); if (fb->buf != MAP_FAILED) return 0; } if (curp->gzip) { /* * Duplicating the file descriptor is required * because we will have to call gzclose(3) * to free memory used internally by zlib, * but that will also close the file descriptor, * which this function must not do. */ if ((fd = dup(fd)) == -1) { mandoc_msg(MANDOCERR_DUP, 0, 0, "%s", strerror(errno)); return -1; } if ((gz = gzdopen(fd, "rb")) == NULL) { mandoc_msg(MANDOCERR_GZDOPEN, 0, 0, "%s", strerror(errno)); close(fd); return -1; } } else gz = NULL; /* * If this isn't a regular file (like, say, stdin), then we must * go the old way and just read things in bit by bit. */ *with_mmap = 0; off = 0; retval = -1; fb->sz = 0; fb->buf = NULL; for (;;) { if (off == fb->sz) { if (fb->sz == (1U << 31)) { mandoc_msg(MANDOCERR_TOOLARGE, 0, 0, NULL); break; } resize_buf(fb, 65536); } ssz = curp->gzip ? gzread(gz, fb->buf + (int)off, fb->sz - off) : read(fd, fb->buf + (int)off, fb->sz - off); if (ssz == 0) { fb->sz = off; retval = 0; break; } if (ssz == -1) { if (curp->gzip) (void)gzerror(gz, &gzerrnum); mandoc_msg(MANDOCERR_READ, 0, 0, "%s", curp->gzip && gzerrnum != Z_ERRNO ? zError(gzerrnum) : strerror(errno)); break; } off += (size_t)ssz; } if (curp->gzip && (gzerrnum = gzclose(gz)) != Z_OK) mandoc_msg(MANDOCERR_GZCLOSE, 0, 0, "%s", gzerrnum == Z_ERRNO ? strerror(errno) : zError(gzerrnum)); if (retval == -1) { free(fb->buf); fb->buf = NULL; } return retval; } static void mparse_end(struct mparse *curp) { if (curp->man->meta.macroset == MACROSET_NONE) curp->man->meta.macroset = MACROSET_MAN; if (curp->man->meta.macroset == MACROSET_MDOC) mdoc_endparse(curp->man); else man_endparse(curp->man); roff_endparse(curp->roff); } /* * Read the whole file into memory and call the parsers. * Called recursively when an .so request is encountered. */ void mparse_readfd(struct mparse *curp, int fd, const char *filename) { static int recursion_depth; struct buf blk; struct buf *save_primary; const char *save_filename, *cp; size_t offset; int save_filenc, save_lineno; int with_mmap; if (recursion_depth > 64) { mandoc_msg(MANDOCERR_ROFFLOOP, curp->line, 0, NULL); return; } else if (recursion_depth == 0 && (cp = strrchr(filename, '.')) != NULL && cp[1] >= '1' && cp[1] <= '9') curp->man->filesec = cp[1]; else curp->man->filesec = '\0'; if (read_whole_file(curp, fd, &blk, &with_mmap) == -1) return; /* * Save some properties of the parent file. */ save_primary = curp->primary; save_filenc = curp->filenc; save_lineno = curp->line; save_filename = mandoc_msg_getinfilename(); curp->primary = &blk; curp->filenc = curp->options & (MPARSE_UTF8 | MPARSE_LATIN1); curp->line = 1; mandoc_msg_setinfilename(filename); /* Skip an UTF-8 byte order mark. */ if (curp->filenc & MPARSE_UTF8 && blk.sz > 2 && (unsigned char)blk.buf[0] == 0xef && (unsigned char)blk.buf[1] == 0xbb && (unsigned char)blk.buf[2] == 0xbf) { offset = 3; curp->filenc &= ~MPARSE_LATIN1; } else offset = 0; recursion_depth++; mparse_buf_r(curp, blk, offset, 1); if (--recursion_depth == 0) mparse_end(curp); /* * Clean up and restore saved parent properties. */ if (with_mmap) munmap(blk.buf, blk.sz); else free(blk.buf); curp->primary = save_primary; curp->filenc = save_filenc; curp->line = save_lineno; if (save_filename != NULL) mandoc_msg_setinfilename(save_filename); } int mparse_open(struct mparse *curp, const char *file) { - char *cp; + const char *cp; int fd, save_errno; cp = strrchr(file, '.'); curp->gzip = (cp != NULL && ! strcmp(cp + 1, "gz")); /* First try to use the filename as it is. */ if ((fd = open(file, O_RDONLY)) != -1) return fd; /* * If that doesn't work and the filename doesn't * already end in .gz, try appending .gz. */ if ( ! curp->gzip) { + char *fn; + save_errno = errno; - mandoc_asprintf(&cp, "%s.gz", file); - fd = open(cp, O_RDONLY); - free(cp); + mandoc_asprintf(&fn, "%s.gz", file); + fd = open(fn, O_RDONLY); + free(fn); errno = save_errno; if (fd != -1) { curp->gzip = 1; return fd; } } /* Neither worked, give up. */ return -1; } struct mparse * mparse_alloc(int options, enum mandoc_os os_e, const char *os_s) { struct mparse *curp; curp = mandoc_calloc(1, sizeof(struct mparse)); curp->options = options; curp->os_s = os_s; curp->roff = roff_alloc(options); curp->man = roff_man_alloc(curp->roff, curp->os_s, curp->options & MPARSE_QUICK ? 1 : 0); if (curp->options & MPARSE_MDOC) { curp->man->meta.macroset = MACROSET_MDOC; if (curp->man->mdocmac == NULL) curp->man->mdocmac = roffhash_alloc(MDOC_Dd, MDOC_MAX); } else if (curp->options & MPARSE_MAN) { curp->man->meta.macroset = MACROSET_MAN; if (curp->man->manmac == NULL) curp->man->manmac = roffhash_alloc(MAN_TH, MAN_MAX); } curp->man->meta.first->tok = TOKEN_NONE; curp->man->meta.os_e = os_e; tag_alloc(); return curp; } void mparse_reset(struct mparse *curp) { tag_free(); roff_reset(curp->roff); roff_man_reset(curp->man); free_buf_list(curp->secondary); curp->secondary = NULL; curp->gzip = 0; tag_alloc(); } void mparse_free(struct mparse *curp) { tag_free(); roffhash_free(curp->man->mdocmac); roffhash_free(curp->man->manmac); roff_man_free(curp->man); roff_free(curp->roff); free_buf_list(curp->secondary); free(curp); } struct roff_meta * mparse_result(struct mparse *curp) { roff_state_reset(curp->man); if (curp->options & MPARSE_VALIDATE) { if (curp->man->meta.macroset == MACROSET_MDOC) mdoc_validate(curp->man); else man_validate(curp->man); tag_postprocess(curp->man, curp->man->meta.first); } return &curp->man->meta; } void mparse_copy(const struct mparse *p) { struct buf *buf; for (buf = p->secondary; buf != NULL; buf = buf->next) puts(buf->buf); }