=================================================================== RCS file: /cvs/mandoc/mdoc.c,v retrieving revision 1.248 retrieving revision 1.268 diff -u -p -r1.248 -r1.268 --- mandoc/mdoc.c 2015/04/19 14:00:19 1.248 +++ mandoc/mdoc.c 2017/08/11 16:56:21 1.268 @@ -1,7 +1,7 @@ -/* $Id: mdoc.c,v 1.248 2015/04/19 14:00:19 schwarze Exp $ */ +/* $Id: mdoc.c,v 1.268 2017/08/11 16:56:21 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons - * Copyright (c) 2010, 2012-2015 Ingo Schwarze + * Copyright (c) 2010, 2012-2017 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 @@ -35,41 +35,6 @@ #include "roff_int.h" #include "libmdoc.h" -const char *const __mdoc_macronames[MDOC_MAX + 1] = { - "text", - "Ap", "Dd", "Dt", "Os", - "Sh", "Ss", "Pp", "D1", - "Dl", "Bd", "Ed", "Bl", - "El", "It", "Ad", "An", - "Ar", "Cd", "Cm", "Dv", - "Er", "Ev", "Ex", "Fa", - "Fd", "Fl", "Fn", "Ft", - "Ic", "In", "Li", "Nd", - "Nm", "Op", "Ot", "Pa", - "Rv", "St", "Va", "Vt", - "Xr", "%A", "%B", "%D", - "%I", "%J", "%N", "%O", - "%P", "%R", "%T", "%V", - "Ac", "Ao", "Aq", "At", - "Bc", "Bf", "Bo", "Bq", - "Bsx", "Bx", "Db", "Dc", - "Do", "Dq", "Ec", "Ef", - "Em", "Eo", "Fx", "Ms", - "No", "Ns", "Nx", "Ox", - "Pc", "Pf", "Po", "Pq", - "Qc", "Ql", "Qo", "Qq", - "Re", "Rs", "Sc", "So", - "Sq", "Sm", "Sx", "Sy", - "Tn", "Ux", "Xc", "Xo", - "Fo", "Fc", "Oo", "Oc", - "Bk", "Ek", "Bt", "Hf", - "Fr", "Ud", "Lb", "Lp", - "Lk", "Mt", "Brq", "Bro", - "Brc", "%C", "Es", "En", - "Dx", "%Q", "br", "sp", - "%U", "Ta", "ll", -}; - const char *const __mdoc_argnames[MDOC_ARG_MAX] = { "split", "nosplit", "ragged", "unfilled", "literal", "file", @@ -80,47 +45,13 @@ const char *const __mdoc_argnames[MDOC_ARG_MAX] = { "width", "compact", "std", "filled", "words", "emphasis", "symbolic", "nested", "centered" - }; - -const char * const *mdoc_macronames = __mdoc_macronames + 1; +}; 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); -void -mdoc_endparse(struct roff_man *mdoc) -{ - - mdoc_macroend(mdoc); -} - -void -mdoc_addeqn(struct roff_man *mdoc, const struct eqn *ep) -{ - struct roff_node *n; - - n = roff_node_alloc(mdoc, ep->ln, ep->pos, ROFFT_EQN, TOKEN_NONE); - n->eqn = ep; - if (ep->ln > mdoc->last->line) - n->flags |= MDOC_LINE; - roff_node_append(mdoc, n); - mdoc->next = ROFF_NEXT_SIBLING; -} - -void -mdoc_addspan(struct roff_man *mdoc, const struct tbl_span *sp) -{ - struct roff_node *n; - - n = roff_node_alloc(mdoc, sp->line, 0, ROFFT_TBL, TOKEN_NONE); - n->span = sp; - roff_node_append(mdoc, n); - mdoc_valid_post(mdoc); - mdoc->next = ROFF_NEXT_SIBLING; -} - /* * Main parse routine. Parses a single line -- really just hands off to * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). @@ -143,39 +74,20 @@ mdoc_parseln(struct roff_man *mdoc, int ln, char *buf, else mdoc->flags &= ~MDOC_SYNOPSIS; - return(roff_getcontrol(mdoc->roff, buf, &offs) ? + return roff_getcontrol(mdoc->roff, buf, &offs) ? mdoc_pmacro(mdoc, ln, buf, offs) : - mdoc_ptext(mdoc, ln, buf, offs)); + mdoc_ptext(mdoc, ln, buf, offs); } void mdoc_macro(MACRO_PROT_ARGS) { - assert(tok > TOKEN_NONE && tok < MDOC_MAX); - - if (mdoc->flags & MDOC_PBODY) { - if (tok == MDOC_Dt) { - mandoc_vmsg(MANDOCERR_DT_LATE, - mdoc->parse, line, ppos, - "Dt %s", buf + *pos); - return; - } - } else if ( ! (mdoc_macros[tok].flags & MDOC_PROLOGUE)) { - if (mdoc->meta.title == NULL) { - mandoc_vmsg(MANDOCERR_DT_NOTITLE, - mdoc->parse, line, ppos, "%s %s", - mdoc_macronames[tok], buf + *pos); - mdoc->meta.title = mandoc_strdup("UNTITLED"); - } - if (NULL == mdoc->meta.vol) - mdoc->meta.vol = mandoc_strdup("LOCAL"); - mdoc->flags |= MDOC_PBODY; - } + assert(tok >= MDOC_Dd && tok < MDOC_MAX); (*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf); } void -mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, int tok) +mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok) { struct roff_node *p; @@ -185,25 +97,25 @@ mdoc_tail_alloc(struct roff_man *mdoc, int line, int p } struct roff_node * -mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos, int tok, - struct roff_node *body, enum mdoc_endbody end) +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 |= MDOC_ENDED; - body->parent->flags |= MDOC_ENDED; + 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 = end; + p->end = ENDBODY_SPACE; roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_SIBLING; - return(p); + return p; } struct roff_node * mdoc_block_alloc(struct roff_man *mdoc, int line, int pos, - int tok, struct mdoc_arg *args) + enum roff_tok tok, struct mdoc_arg *args) { struct roff_node *p; @@ -214,13 +126,9 @@ mdoc_block_alloc(struct roff_man *mdoc, int line, int switch (tok) { case MDOC_Bd: - /* FALLTHROUGH */ case MDOC_Bf: - /* FALLTHROUGH */ case MDOC_Bl: - /* FALLTHROUGH */ case MDOC_En: - /* FALLTHROUGH */ case MDOC_Rs: p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); break; @@ -229,12 +137,12 @@ mdoc_block_alloc(struct roff_man *mdoc, int line, int } roff_node_append(mdoc, p); mdoc->next = ROFF_NEXT_CHILD; - return(p); + return p; } void mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos, - int tok, struct mdoc_arg *args) + enum roff_tok tok, struct mdoc_arg *args) { struct roff_node *p; @@ -255,37 +163,11 @@ mdoc_elem_alloc(struct roff_man *mdoc, int line, int p } void -mdoc_word_alloc(struct roff_man *mdoc, int line, int pos, const char *p) -{ - struct roff_node *n; - - n = roff_node_alloc(mdoc, line, pos, ROFFT_TEXT, TOKEN_NONE); - n->string = roff_strdup(mdoc->roff, p); - roff_node_append(mdoc, n); - mdoc_valid_post(mdoc); - mdoc->next = ROFF_NEXT_SIBLING; -} - -void -mdoc_word_append(struct roff_man *mdoc, const char *p) -{ - struct roff_node *n; - char *addstr, *newstr; - - n = mdoc->last; - addstr = roff_strdup(mdoc->roff, p); - mandoc_asprintf(&newstr, "%s %s", n->string, addstr); - free(addstr); - free(n->string); - n->string = newstr; - mdoc->next = ROFF_NEXT_SIBLING; -} - -void mdoc_node_relink(struct roff_man *mdoc, struct roff_node *p) { roff_node_unlink(mdoc, p); + p->prev = p->next = NULL; roff_node_append(mdoc, p); } @@ -297,36 +179,27 @@ 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; - assert(mdoc->last); n = mdoc->last; /* - * Divert directly to list processing if we're encountering a - * columnar ROFFT_BLOCK with or without a prior ROFFT_BLOCK entry - * (a ROFFT_BODY means it's already open, in which case we should - * process within its context in the normal way). + * 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) { - /* `Bl' is open without any children. */ + 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, MDOC_It, line, offs, &offs, buf); - return(1); + return 1; } - if (n->tok == MDOC_It && n->type == ROFFT_BLOCK && - NULL != n->parent && - MDOC_Bl == n->parent->tok && - LIST_column == n->parent->norm->Bl.type) { - /* `Bl' has block-level `It' children. */ - mdoc->flags |= MDOC_FREECOL; - mdoc_macro(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). @@ -372,25 +245,40 @@ mdoc_ptext(struct roff_man *mdoc, int line, char *buf, mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse, 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 & MDOC_LITERAL)) { + 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, mdoc->parse, line, (int)(c - buf), NULL); - - /* - * Insert a `sp' in the case of a blank line. Technically, - * blank lines aren't allowed, but enough manuals assume this - * behaviour that we want to work around it. - */ - mdoc_elem_alloc(mdoc, line, offs, MDOC_sp, NULL); + roff_elem_alloc(mdoc, line, offs, ROFF_sp); + mdoc->last->flags |= NODE_VALID | NODE_ENDED; mdoc->next = ROFF_NEXT_SIBLING; - mdoc_valid_post(mdoc); - return(1); + return 1; } - mdoc_word_alloc(mdoc, line, offs, buf+offs); + roff_word_alloc(mdoc, line, offs, buf+offs); if (mdoc->flags & MDOC_LITERAL) - return(1); + return 1; /* * End-of-sentence check. If the last character is an unescaped @@ -401,8 +289,30 @@ mdoc_ptext(struct roff_man *mdoc, int line, char *buf, assert(buf < end); if (mandoc_eos(buf+offs, (size_t)(end-buf-offs))) - mdoc->last->flags |= MDOC_EOS; - return(1); + 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, mdoc->parse, + line, (int)(c - buf), NULL); + } + + return 1; } /* @@ -414,29 +324,22 @@ mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, { struct roff_node *n; const char *cp; - int tok; - int i, sv; - char mac[5]; + size_t sz; + enum roff_tok tok; + int sv; - sv = offs; + /* Determine the line macro. */ - /* - * Copy the first word into a nil-terminated buffer. - * Stop when a space, tab, escape, or eoln is encountered. - */ - - i = 0; - while (i < 4 && strchr(" \t\\", buf[offs]) == NULL) - mac[i++] = buf[offs++]; - - mac[i] = '\0'; - - tok = (i > 1 && i < 4) ? mdoc_hash_find(mac) : TOKEN_NONE; - + 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, mdoc->parse, ln, sv, buf + sv - 1); - return(1); + return 1; } /* Skip a leading escape sequence or tab. */ @@ -456,7 +359,7 @@ mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, /* Jump to the next non-whitespace word. */ - while (buf[offs] && ' ' == buf[offs]) + while (buf[offs] == ' ') offs++; /* @@ -473,41 +376,28 @@ mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, * into macro processing. */ - if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok) { + n = mdoc->last; + if (n == NULL || tok == MDOC_It || tok == MDOC_El) { mdoc_macro(mdoc, tok, ln, sv, &offs, buf); - return(1); + return 1; } - n = mdoc->last; - assert(mdoc->last); - /* - * If the first macro of a `Bl -column', open an `It' block - * context around the parsed macro. + * 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) { + 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, MDOC_It, ln, sv, &sv, buf); - return(1); + return 1; } - /* - * If we're following a block-level `It' within a `Bl -column' - * context (perhaps opened in the above block or in ptext()), - * then open an `It' block context around the parsed macro. - */ - - if (n->tok == MDOC_It && n->type == ROFFT_BLOCK && - NULL != n->parent && - MDOC_Bl == n->parent->tok && - LIST_column == n->parent->norm->Bl.type) { - mdoc->flags |= MDOC_FREECOL; - mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf); - return(1); - } - /* Normal processing of a macro. */ mdoc_macro(mdoc, tok, ln, sv, &offs, buf); @@ -516,9 +406,9 @@ mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, if (mdoc->quick && MDOC_Sh == tok && SEC_NAME != mdoc->last->sec) - return(2); + return 2; - return(1); + return 1; } enum mdelim @@ -526,82 +416,44 @@ mdoc_isdelim(const char *p) { if ('\0' == p[0]) - return(DELIM_NONE); + return DELIM_NONE; if ('\0' == p[1]) switch (p[0]) { case '(': - /* FALLTHROUGH */ case '[': - return(DELIM_OPEN); + return DELIM_OPEN; case '|': - return(DELIM_MIDDLE); + return DELIM_MIDDLE; case '.': - /* FALLTHROUGH */ case ',': - /* FALLTHROUGH */ case ';': - /* FALLTHROUGH */ case ':': - /* FALLTHROUGH */ case '?': - /* FALLTHROUGH */ case '!': - /* FALLTHROUGH */ case ')': - /* FALLTHROUGH */ case ']': - return(DELIM_CLOSE); + return DELIM_CLOSE; default: - return(DELIM_NONE); + return DELIM_NONE; } if ('\\' != p[0]) - return(DELIM_NONE); + return DELIM_NONE; if (0 == strcmp(p + 1, ".")) - return(DELIM_CLOSE); + return DELIM_CLOSE; if (0 == strcmp(p + 1, "fR|\\fP")) - return(DELIM_MIDDLE); + return DELIM_MIDDLE; - return(DELIM_NONE); + return DELIM_NONE; } void -mdoc_deroff(char **dest, const struct roff_node *n) +mdoc_validate(struct roff_man *mdoc) { - char *cp; - size_t sz; - if (n->type != ROFFT_TEXT) { - for (n = n->child; n; n = n->next) - mdoc_deroff(dest, n); - return; - } - - /* Skip leading whitespace. */ - - for (cp = n->string; '\0' != *cp; cp++) - if (0 == isspace((unsigned char)*cp)) - break; - - /* Skip trailing whitespace. */ - - for (sz = strlen(cp); sz; sz--) - if (0 == isspace((unsigned char)cp[sz-1])) - break; - - /* Skip empty strings. */ - - if (0 == sz) - return; - - if (NULL == *dest) { - *dest = mandoc_strndup(cp, sz); - return; - } - - mandoc_asprintf(&cp, "%s %*s", *dest, (int)sz, cp); - free(*dest); - *dest = cp; + mdoc->last = mdoc->first; + mdoc_node_validate(mdoc); + mdoc_state_reset(mdoc); }