=================================================================== RCS file: /cvs/mandoc/mdoc_validate.c,v retrieving revision 1.122 retrieving revision 1.130 diff -u -p -r1.122 -r1.130 --- mandoc/mdoc_validate.c 2010/11/29 12:22:28 1.122 +++ mandoc/mdoc_validate.c 2010/11/30 10:32:05 1.130 @@ -1,4 +1,4 @@ -/* $Id: mdoc_validate.c,v 1.122 2010/11/29 12:22:28 kristaps Exp $ */ +/* $Id: mdoc_validate.c,v 1.130 2010/11/30 10:32:05 kristaps Exp $ */ /* * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons * @@ -85,6 +85,9 @@ static int post_at(POST_ARGS); static int post_bf(POST_ARGS); static int post_bl(POST_ARGS); static int post_bl_head(POST_ARGS); +static int post_defaults(POST_ARGS); +static int post_literal(POST_ARGS); +static int post_eoln(POST_ARGS); static int post_dt(POST_ARGS); static int post_it(POST_ARGS); static int post_lb(POST_ARGS); @@ -95,7 +98,6 @@ static int post_sh(POST_ARGS); static int post_sh_body(POST_ARGS); static int post_sh_head(POST_ARGS); static int post_st(POST_ARGS); -static int post_eoln(POST_ARGS); static int post_vt(POST_ARGS); static int pre_an(PRE_ARGS); static int pre_bd(PRE_ARGS); @@ -104,19 +106,23 @@ static int pre_dd(PRE_ARGS); static int pre_display(PRE_ARGS); static int pre_dt(PRE_ARGS); static int pre_it(PRE_ARGS); +static int pre_literal(PRE_ARGS); static int pre_os(PRE_ARGS); -static int pre_pp(PRE_ARGS); +static int pre_par(PRE_ARGS); static int pre_rv(PRE_ARGS); static int pre_sh(PRE_ARGS); static int pre_ss(PRE_ARGS); static v_post posts_an[] = { post_an, NULL }; -static v_post posts_at[] = { post_at, NULL }; -static v_post posts_bd_bk[] = { hwarn_eq0, bwarn_ge1, NULL }; +static v_post posts_at[] = { post_at, post_defaults, NULL }; +static v_post posts_bd[] = { post_literal, hwarn_eq0, bwarn_ge1, NULL }; static v_post posts_bf[] = { hwarn_le1, post_bf, NULL }; +static v_post posts_bk[] = { hwarn_eq0, bwarn_ge1, NULL }; static v_post posts_bl[] = { bwarn_ge1, post_bl, NULL }; static v_post posts_bool[] = { eerr_eq1, ebool, NULL }; static v_post posts_eoln[] = { post_eoln, NULL }; +static v_post posts_defaults[] = { post_defaults, NULL }; +static v_post posts_dl[] = { post_literal, bwarn_ge1, herr_eq0, NULL }; static v_post posts_dt[] = { post_dt, NULL }; static v_post posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL }; static v_post posts_it[] = { post_it, NULL }; @@ -135,9 +141,10 @@ static v_post posts_vt[] = { post_vt, NULL }; static v_post posts_wline[] = { bwarn_ge1, herr_eq0, NULL }; static v_post posts_wtext[] = { ewarn_ge1, NULL }; static v_pre pres_an[] = { pre_an, NULL }; -static v_pre pres_bd[] = { pre_display, pre_bd, pre_pp, NULL }; -static v_pre pres_bl[] = { pre_bl, pre_pp, NULL }; +static v_pre pres_bd[] = { pre_display, pre_bd, pre_literal, pre_par, NULL }; +static v_pre pres_bl[] = { pre_bl, pre_par, NULL }; static v_pre pres_d1[] = { pre_display, NULL }; +static v_pre pres_dl[] = { pre_literal, pre_display, NULL }; static v_pre pres_dd[] = { pre_dd, NULL }; static v_pre pres_dt[] = { pre_dt, NULL }; static v_pre pres_er[] = { NULL, NULL }; @@ -145,7 +152,7 @@ static v_pre pres_ex[] = { NULL, NULL }; static v_pre pres_fd[] = { NULL, NULL }; static v_pre pres_it[] = { pre_it, NULL }; static v_pre pres_os[] = { pre_os, NULL }; -static v_pre pres_pp[] = { pre_pp, NULL }; +static v_pre pres_pp[] = { pre_par, NULL }; static v_pre pres_rv[] = { pre_rv, NULL }; static v_pre pres_sh[] = { pre_sh, NULL }; static v_pre pres_ss[] = { pre_ss, NULL }; @@ -159,15 +166,15 @@ const struct valids mdoc_valids[MDOC_MAX] = { { pres_ss, posts_ss }, /* Ss */ { pres_pp, posts_notext }, /* Pp */ { pres_d1, posts_wline }, /* D1 */ - { pres_d1, posts_wline }, /* Dl */ - { pres_bd, posts_bd_bk }, /* Bd */ + { pres_dl, posts_dl }, /* Dl */ + { pres_bd, posts_bd }, /* Bd */ { NULL, NULL }, /* Ed */ { pres_bl, posts_bl }, /* Bl */ { NULL, NULL }, /* El */ { pres_it, posts_it }, /* It */ { NULL, posts_text }, /* Ad */ { pres_an, posts_an }, /* An */ - { NULL, NULL }, /* Ar */ + { NULL, posts_defaults }, /* Ar */ { NULL, posts_text }, /* Cd */ { NULL, NULL }, /* Cm */ { NULL, NULL }, /* Dv */ @@ -181,12 +188,12 @@ const struct valids mdoc_valids[MDOC_MAX] = { { NULL, posts_wtext }, /* Ft */ { NULL, posts_text }, /* Ic */ { NULL, posts_text1 }, /* In */ - { NULL, NULL }, /* Li */ + { NULL, posts_defaults }, /* Li */ { NULL, posts_nd }, /* Nd */ { NULL, posts_nm }, /* Nm */ { NULL, posts_wline }, /* Op */ { NULL, NULL }, /* Ot */ - { NULL, NULL }, /* Pa */ + { NULL, posts_defaults }, /* Pa */ { pres_rv, NULL }, /* Rv */ { NULL, posts_st }, /* St */ { NULL, NULL }, /* Va */ @@ -251,7 +258,7 @@ const struct valids mdoc_valids[MDOC_MAX] = { { NULL, NULL }, /* Fc */ { NULL, NULL }, /* Oo */ { NULL, NULL }, /* Oc */ - { NULL, posts_bd_bk }, /* Bk */ + { NULL, posts_bk }, /* Bk */ { NULL, NULL }, /* Ek */ { NULL, posts_eoln }, /* Bt */ { NULL, NULL }, /* Hf */ @@ -260,7 +267,7 @@ const struct valids mdoc_valids[MDOC_MAX] = { { NULL, posts_lb }, /* Lb */ { NULL, posts_notext }, /* Lp */ { NULL, posts_text }, /* Lk */ - { NULL, posts_text }, /* Mt */ + { NULL, posts_defaults }, /* Mt */ { NULL, posts_wline }, /* Brq */ { NULL, NULL }, /* Bro */ { NULL, NULL }, /* Brc */ @@ -580,6 +587,7 @@ pre_display(PRE_ARGS) if (MDOC_BLOCK == node->type) if (MDOC_Bd == node->tok) break; + if (NULL == node) return(1); @@ -1075,17 +1083,38 @@ post_bf(POST_ARGS) return(1); } - static int post_lb(POST_ARGS) { + const char *p; + char *buf; + size_t sz; - if (mdoc_a2lib(mdoc->last->child->string)) + assert(mdoc->last->child); + assert(MDOC_TEXT == mdoc->last->child->type); + + p = mdoc_a2lib(mdoc->last->child->string); + + /* If lookup ok, replace with table value. */ + + if (p) { + free(mdoc->last->child->string); + mdoc->last->child->string = mandoc_strdup(p); return(1); - return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADLIB)); -} + } + /* If not, use "library ``xxxx''. */ + sz = strlen(mdoc->last->child->string) + + 2 + strlen("\\(lqlibrary\\(rq"); + buf = mandoc_malloc(sz); + snprintf(buf, sz, "library \\(lq%s\\(rq", + mdoc->last->child->string); + free(mdoc->last->child->string); + mdoc->last->child->string = buf; + return(1); +} + static int post_eoln(POST_ARGS) { @@ -1125,28 +1154,146 @@ post_vt(POST_ARGS) static int post_nm(POST_ARGS) { + struct mdoc_node *nn; + char buf[BUFSIZ]; - if (mdoc->last->child) + /* If no child specified, make sure we have the meta name. */ + + if (NULL == mdoc->last->child && NULL == mdoc->meta.name) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME); return(1); - if (mdoc->meta.name) + } else if (mdoc->meta.name) return(1); - return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME)); + + /* If no meta name, set it from the child. */ + + buf[0] = '\0'; + + for (nn = mdoc->last->child; nn; nn = nn->next) { + /* XXX - copied from concat(). */ + assert(MDOC_TEXT == nn->type); + + if (strlcat(buf, nn->string, BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, nn, MANDOCERR_MEM); + return(0); + } + + if (NULL == nn->next) + continue; + + if (strlcat(buf, " ", BUFSIZ) >= BUFSIZ) { + mdoc_nmsg(mdoc, nn, MANDOCERR_MEM); + return(0); + } + } + + mdoc->meta.name = mandoc_strdup(buf); + return(1); } +static int +post_literal(POST_ARGS) +{ + + /* + * The `Dl' (note "el" not "one") and `Bd' macros unset the + * MDOC_LITERAL flag as they leave. Note that `Bd' only sets + * this in literal mode, but it doesn't hurt to just switch it + * off in general since displays can't be nested. + */ + if (MDOC_BODY == mdoc->last->type) + mdoc->last->flags &= ~MDOC_LITERAL; + + return(1); +} + static int +post_defaults(POST_ARGS) +{ + struct mdoc_node *nn; + + /* + * The `Ar' defaults to "file ..." if no value is provided as an + * argument; the `Mt' and `Pa' macros use "~"; the `Li' just + * gets an empty string. + */ + + if (mdoc->last->child) + return(1); + + nn = mdoc->last; + mdoc->next = MDOC_NEXT_CHILD; + + switch (nn->tok) { + case (MDOC_Ar): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "file")) + return(0); + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "...")) + return(0); + break; + case (MDOC_At): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "AT&T")) + return(0); + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "UNIX")) + return(0); + break; + case (MDOC_Li): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "")) + return(0); + break; + case (MDOC_Pa): + /* FALLTHROUGH */ + case (MDOC_Mt): + if ( ! mdoc_word_alloc(mdoc, nn->line, nn->pos, "~")) + return(0); + break; + default: + abort(); + /* NOTREACHED */ + } + + mdoc->last = nn; + return(1); +} + +static int post_at(POST_ARGS) { + const char *p, *q; + char *buf; + size_t sz; + /* + * If we have a child, look it up in the standard keys. If a + * key exist, use that instead of the child; if it doesn't, + * prefix "AT&T UNIX " to the existing data. + */ + if (NULL == mdoc->last->child) return(1); + assert(MDOC_TEXT == mdoc->last->child->type); - if (mdoc_a2att(mdoc->last->child->string)) - return(1); - return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADATT)); -} + p = mdoc_a2att(mdoc->last->child->string); + if (p) { + free(mdoc->last->child->string); + mdoc->last->child->string = mandoc_strdup(p); + } else { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADATT); + p = "AT&T UNIX "; + q = mdoc->last->child->string; + sz = strlen(p) + strlen(q) + 1; + buf = mandoc_malloc(sz); + strlcpy(buf, p, sz); + strlcat(buf, q, sz); + free(mdoc->last->child->string); + mdoc->last->child->string = buf; + } + return(1); +} + static int post_an(POST_ARGS) { @@ -1255,24 +1402,69 @@ post_it(POST_ARGS) static int post_bl_head(POST_ARGS) { - struct mdoc_node *n; + struct mdoc_node *np, *nn, *nnp; + int i, j; - assert(mdoc->last->parent); - n = mdoc->last->parent; + if (LIST_column != mdoc->last->data.Bl->type) + /* FIXME: this should be ERROR class... */ + return(hwarn_eq0(mdoc)); - if (LIST_column == n->data.Bl->type) { - if (n->data.Bl->ncols && mdoc->last->nchild) { - mdoc_nmsg(mdoc, n, MANDOCERR_COLUMNS); - return(0); - } + /* + * Convert old-style lists, where the column width specifiers + * trail as macro parameters, to the new-style ("normal-form") + * lists where they're argument values following -column. + */ + + /* First, disallow both types and allow normal-form. */ + + /* + * TODO: technically, we can accept both and just merge the two + * lists, but I'll leave that for another day. + */ + + if (mdoc->last->data.Bl->ncols && mdoc->last->nchild) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_COLUMNS); + return(0); + } else if (NULL == mdoc->last->child) return(1); + + np = mdoc->last->parent; + assert(np->args); + + for (j = 0; j < (int)np->args->argc; j++) + if (MDOC_Column == np->args->argv[j].arg) + break; + + assert(j < (int)np->args->argc); + assert(0 == np->args->argv[j].sz); + + /* + * Accomodate for new-style groff column syntax. Shuffle the + * child nodes, all of which must be TEXT, as arguments for the + * column field. Then, delete the head children. + */ + + np->args->argv[j].sz = (size_t)mdoc->last->nchild; + np->args->argv[j].value = mandoc_malloc + ((size_t)mdoc->last->nchild * sizeof(char *)); + + mdoc->last->data.Bl->ncols = np->args->argv[j].sz; + mdoc->last->data.Bl->cols = (const char **)np->args->argv[j].value; + + for (i = 0, nn = mdoc->last->child; nn; i++) { + np->args->argv[j].value[i] = nn->string; + nn->string = NULL; + nnp = nn; + nn = nn->next; + mdoc_node_delete(NULL, nnp); } - /* FIXME: should be ERROR class. */ - return(hwarn_eq0(mdoc)); + mdoc->last->nchild = 0; + mdoc->last->child = NULL; + + return(1); } - static int post_bl(POST_ARGS) { @@ -1345,17 +1537,26 @@ post_root(POST_ARGS) return(0); } - static int post_st(POST_ARGS) { + const char *p; - if (mdoc_a2st(mdoc->last->child->string)) - return(1); - return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADSTANDARD)); -} + assert(MDOC_TEXT == mdoc->last->child->type); + p = mdoc_a2st(mdoc->last->child->string); + if (p == NULL) { + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADSTANDARD); + mdoc_node_delete(mdoc, mdoc->last); + } else { + free(mdoc->last->child->string); + mdoc->last->child->string = mandoc_strdup(p); + } + + return(1); +} + static int post_rs(POST_ARGS) { @@ -1502,34 +1703,33 @@ post_sh_body(POST_ARGS) static int post_sh_head(POST_ARGS) { - char buf[BUFSIZ]; - enum mdoc_sec sec; - const struct mdoc_node *n; + char buf[BUFSIZ]; + enum mdoc_sec sec; + struct mdoc_node *n; /* * Process a new section. Sections are either "named" or - * "custom"; custom sections are user-defined, while named ones - * usually follow a conventional order and may only appear in - * certain manual sections. + * "custom". Custom sections are user-defined, while named ones + * follow a conventional order and may only appear in certain + * manual sections. */ buf[0] = '\0'; - /* - * FIXME: yes, these can use a dynamic buffer, but I don't do so - * in the interests of simplicity. - */ + /* FIXME: use dynamic buffer... */ for (n = mdoc->last->child; n; n = n->next) { - /* XXX - copied from compact(). */ + /* XXX - copied from concat(). */ assert(MDOC_TEXT == n->type); if (strlcat(buf, n->string, BUFSIZ) >= BUFSIZ) { mdoc_nmsg(mdoc, n, MANDOCERR_MEM); return(0); } + if (NULL == n->next) continue; + if (strlcat(buf, " ", BUFSIZ) >= BUFSIZ) { mdoc_nmsg(mdoc, n, MANDOCERR_MEM); return(0); @@ -1538,41 +1738,60 @@ post_sh_head(POST_ARGS) sec = mdoc_str2sec(buf); - /* - * Check: NAME should always be first, CUSTOM has no roles, - * non-CUSTOM has a conventional order to be followed. - */ + /* The NAME should be first. */ if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed) - if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST)) - return(0); + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST); + /* The SYNOPSIS gets special attention in other areas. */ + + if (SEC_SYNOPSIS == sec) + mdoc->flags |= MDOC_SYNOPSIS; + else + mdoc->flags &= ~MDOC_SYNOPSIS; + + /* Mark our last section. */ + + mdoc->lastsec = sec; + + /* We don't care about custom sections after this. */ + if (SEC_CUSTOM == sec) return(1); + /* + * Check whether our non-custom section is being repeated or is + * out of order. + */ + if (sec == mdoc->lastnamed) - if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP)) - return(0); + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP); if (sec < mdoc->lastnamed) - if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO)) - return(0); + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO); - /* - * Check particular section/manual conventions. LIBRARY can - * only occur in manual section 2, 3, and 9. - */ + /* Mark the last named section. */ + mdoc->lastnamed = sec; + + /* Check particular section/manual conventions. */ + + assert(mdoc->meta.msec); + switch (sec) { + case (SEC_RETURN_VALUES): + /* FALLTHROUGH */ + case (SEC_ERRORS): + /* FALLTHROUGH */ case (SEC_LIBRARY): - assert(mdoc->meta.msec); if (*mdoc->meta.msec == '2') break; if (*mdoc->meta.msec == '3') break; if (*mdoc->meta.msec == '9') break; - return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECMSEC)); + mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECMSEC); + break; default: break; } @@ -1580,15 +1799,17 @@ post_sh_head(POST_ARGS) return(1); } - static int -pre_pp(PRE_ARGS) +pre_par(PRE_ARGS) { if (NULL == mdoc->last) return(1); - /* Don't allow prior `Lp' or `Pp'. */ + /* + * Don't allow prior `Lp' or `Pp' prior to a paragraph-type + * block: `Lp', `Pp', or non-compact `Bd' or `Bl'. + */ if (MDOC_Pp != mdoc->last->tok && MDOC_Lp != mdoc->last->tok) return(1); @@ -1600,5 +1821,36 @@ pre_pp(PRE_ARGS) mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_IGNPAR); mdoc_node_delete(mdoc, mdoc->last); + return(1); +} + +static int +pre_literal(PRE_ARGS) +{ + + if (MDOC_BODY != n->type) + return(1); + + /* + * The `Dl' (note "el" not "one") and `Bd -literal' and `Bd + * -unfilled' macros set MDOC_LITERAL on entrance to the body. + */ + + switch (n->tok) { + case (MDOC_Dl): + mdoc->flags |= MDOC_LITERAL; + break; + case (MDOC_Bd): + assert(n->data.Bd); + if (DISP_literal == n->data.Bd->type) + mdoc->flags |= MDOC_LITERAL; + if (DISP_unfilled == n->data.Bd->type) + mdoc->flags |= MDOC_LITERAL; + break; + default: + abort(); + /* NOTREACHED */ + } + return(1); }