File: [local] / src / usr.bin / mandoc / mdoc_validate.c (download)
Revision 1.63, Sun Jun 27 21:54:42 2010 UTC (13 years, 11 months ago) by schwarze
Branch: MAIN
Changes since 1.62: +3 -1 lines
Full .nr nS support, unbreaking the kernel manuals.
Kristaps coded this from scratch after reading my .nr patch;
it is simpler and more powerful.
Registers live in struct regset in regs.h, struct man and struct mdoc
contain pointers to it. The nS register is cleared when parsing .Sh.
Frontends respect the MDOC_SYNPRETTY flag set in mdoc node_alloc.
|
/* $Id: mdoc_validate.c,v 1.63 2010/06/27 21:54:42 schwarze Exp $ */
/*
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@bsd.lv>
*
* 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 <sys/types.h>
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mandoc.h"
#include "libmdoc.h"
#include "libmandoc.h"
/* FIXME: .Bl -diag can't have non-text children in HEAD. */
/* TODO: ignoring Pp (it's superfluous in some invocations). */
#define PRE_ARGS struct mdoc *mdoc, struct mdoc_node *n
#define POST_ARGS struct mdoc *mdoc
typedef int (*v_pre)(PRE_ARGS);
typedef int (*v_post)(POST_ARGS);
struct valids {
v_pre *pre;
v_post *post;
};
static int check_parent(PRE_ARGS, enum mdoct, enum mdoc_type);
static int check_stdarg(PRE_ARGS);
static int check_text(struct mdoc *, int, int, char *);
static int check_argv(struct mdoc *,
struct mdoc_node *, struct mdoc_argv *);
static int check_args(struct mdoc *, struct mdoc_node *);
static int err_child_lt(struct mdoc *, const char *, int);
static int warn_child_lt(struct mdoc *, const char *, int);
static int err_child_gt(struct mdoc *, const char *, int);
static int warn_child_gt(struct mdoc *, const char *, int);
static int err_child_eq(struct mdoc *, const char *, int);
static int warn_child_eq(struct mdoc *, const char *, int);
static int warn_count(struct mdoc *, const char *,
int, const char *, int);
static int err_count(struct mdoc *, const char *,
int, const char *, int);
static int berr_ge1(POST_ARGS);
static int bwarn_ge1(POST_ARGS);
static int ebool(POST_ARGS);
static int eerr_eq0(POST_ARGS);
static int eerr_eq1(POST_ARGS);
static int eerr_ge1(POST_ARGS);
static int eerr_le1(POST_ARGS);
static int ewarn_ge1(POST_ARGS);
static int herr_eq0(POST_ARGS);
static int herr_ge1(POST_ARGS);
static int hwarn_eq1(POST_ARGS);
static int hwarn_eq0(POST_ARGS);
static int hwarn_le1(POST_ARGS);
static int post_an(POST_ARGS);
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_dt(POST_ARGS);
static int post_it(POST_ARGS);
static int post_lb(POST_ARGS);
static int post_nm(POST_ARGS);
static int post_root(POST_ARGS);
static int post_rs(POST_ARGS);
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);
static int pre_bl(PRE_ARGS);
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_os(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_bf[] = { hwarn_le1, post_bf, 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_dt[] = { post_dt, NULL };
static v_post posts_fo[] = { hwarn_eq1, bwarn_ge1, NULL };
static v_post posts_it[] = { post_it, NULL };
static v_post posts_lb[] = { eerr_eq1, post_lb, NULL };
static v_post posts_nd[] = { berr_ge1, NULL };
static v_post posts_nm[] = { post_nm, NULL };
static v_post posts_notext[] = { eerr_eq0, NULL };
static v_post posts_rs[] = { berr_ge1, herr_eq0, post_rs, NULL };
static v_post posts_sh[] = { herr_ge1, bwarn_ge1, post_sh, NULL };
static v_post posts_sp[] = { eerr_le1, NULL };
static v_post posts_ss[] = { herr_ge1, NULL };
static v_post posts_st[] = { eerr_eq1, post_st, NULL };
static v_post posts_text[] = { eerr_ge1, NULL };
static v_post posts_text1[] = { eerr_eq1, NULL };
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, NULL };
static v_pre pres_bl[] = { pre_bl, NULL };
static v_pre pres_d1[] = { 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 };
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_rv[] = { pre_rv, NULL };
static v_pre pres_sh[] = { pre_sh, NULL };
static v_pre pres_ss[] = { pre_ss, NULL };
const struct valids mdoc_valids[MDOC_MAX] = {
{ NULL, NULL }, /* Ap */
{ pres_dd, posts_text }, /* Dd */
{ pres_dt, posts_dt }, /* Dt */
{ pres_os, NULL }, /* Os */
{ pres_sh, posts_sh }, /* Sh */
{ pres_ss, posts_ss }, /* Ss */
{ NULL, posts_notext }, /* Pp */
{ pres_d1, posts_wline }, /* D1 */
{ pres_d1, posts_wline }, /* Dl */
{ pres_bd, posts_bd_bk }, /* 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_text }, /* Cd */
{ NULL, NULL }, /* Cm */
{ NULL, NULL }, /* Dv */
{ pres_er, posts_text }, /* Er */
{ NULL, NULL }, /* Ev */
{ pres_ex, NULL }, /* Ex */
{ NULL, NULL }, /* Fa */
{ pres_fd, posts_wtext }, /* Fd */
{ NULL, NULL }, /* Fl */
{ NULL, posts_text }, /* Fn */
{ NULL, posts_wtext }, /* Ft */
{ NULL, posts_text }, /* Ic */
{ NULL, posts_text1 }, /* In */
{ NULL, NULL }, /* Li */
{ NULL, posts_nd }, /* Nd */
{ NULL, posts_nm }, /* Nm */
{ NULL, posts_wline }, /* Op */
{ NULL, NULL }, /* Ot */
{ NULL, NULL }, /* Pa */
{ pres_rv, NULL }, /* Rv */
{ NULL, posts_st }, /* St */
{ NULL, NULL }, /* Va */
{ NULL, posts_vt }, /* Vt */
{ NULL, posts_wtext }, /* Xr */
{ NULL, posts_text }, /* %A */
{ NULL, posts_text }, /* %B */ /* FIXME: can be used outside Rs/Re. */
{ NULL, posts_text }, /* %D */ /* FIXME: check date with mandoc_a2time(). */
{ NULL, posts_text }, /* %I */
{ NULL, posts_text }, /* %J */
{ NULL, posts_text }, /* %N */
{ NULL, posts_text }, /* %O */
{ NULL, posts_text }, /* %P */
{ NULL, posts_text }, /* %R */
{ NULL, posts_text }, /* %T */ /* FIXME: can be used outside Rs/Re. */
{ NULL, posts_text }, /* %V */
{ NULL, NULL }, /* Ac */
{ NULL, NULL }, /* Ao */
{ NULL, posts_wline }, /* Aq */
{ NULL, posts_at }, /* At */
{ NULL, NULL }, /* Bc */
{ NULL, posts_bf }, /* Bf */
{ NULL, NULL }, /* Bo */
{ NULL, posts_wline }, /* Bq */
{ NULL, NULL }, /* Bsx */
{ NULL, NULL }, /* Bx */
{ NULL, posts_bool }, /* Db */
{ NULL, NULL }, /* Dc */
{ NULL, NULL }, /* Do */
{ NULL, posts_wline }, /* Dq */
{ NULL, NULL }, /* Ec */
{ NULL, NULL }, /* Ef */
{ NULL, NULL }, /* Em */
{ NULL, NULL }, /* Eo */
{ NULL, NULL }, /* Fx */
{ NULL, posts_text }, /* Ms */
{ NULL, posts_notext }, /* No */
{ NULL, posts_notext }, /* Ns */
{ NULL, NULL }, /* Nx */
{ NULL, NULL }, /* Ox */
{ NULL, NULL }, /* Pc */
{ NULL, posts_text1 }, /* Pf */
{ NULL, NULL }, /* Po */
{ NULL, posts_wline }, /* Pq */
{ NULL, NULL }, /* Qc */
{ NULL, posts_wline }, /* Ql */
{ NULL, NULL }, /* Qo */
{ NULL, posts_wline }, /* Qq */
{ NULL, NULL }, /* Re */
{ NULL, posts_rs }, /* Rs */
{ NULL, NULL }, /* Sc */
{ NULL, NULL }, /* So */
{ NULL, posts_wline }, /* Sq */
{ NULL, posts_bool }, /* Sm */
{ NULL, posts_text }, /* Sx */
{ NULL, posts_text }, /* Sy */
{ NULL, posts_text }, /* Tn */
{ NULL, NULL }, /* Ux */
{ NULL, NULL }, /* Xc */
{ NULL, NULL }, /* Xo */
{ NULL, posts_fo }, /* Fo */
{ NULL, NULL }, /* Fc */
{ NULL, NULL }, /* Oo */
{ NULL, NULL }, /* Oc */
{ NULL, posts_bd_bk }, /* Bk */
{ NULL, NULL }, /* Ek */
{ NULL, posts_eoln }, /* Bt */
{ NULL, NULL }, /* Hf */
{ NULL, NULL }, /* Fr */
{ NULL, posts_eoln }, /* Ud */
{ NULL, posts_lb }, /* Lb */
{ NULL, posts_notext }, /* Lp */
{ NULL, posts_text }, /* Lk */
{ NULL, posts_text }, /* Mt */
{ NULL, posts_wline }, /* Brq */
{ NULL, NULL }, /* Bro */
{ NULL, NULL }, /* Brc */
{ NULL, posts_text }, /* %C */
{ NULL, NULL }, /* Es */
{ NULL, NULL }, /* En */
{ NULL, NULL }, /* Dx */
{ NULL, posts_text }, /* %Q */
{ NULL, posts_notext }, /* br */
{ NULL, posts_sp }, /* sp */
{ NULL, posts_text1 }, /* %U */
{ NULL, NULL }, /* Ta */
};
int
mdoc_valid_pre(struct mdoc *mdoc, struct mdoc_node *n)
{
v_pre *p;
int line, pos;
char *tp;
if (MDOC_TEXT == n->type) {
tp = n->string;
line = n->line;
pos = n->pos;
return(check_text(mdoc, line, pos, tp));
}
if ( ! check_args(mdoc, n))
return(0);
if (NULL == mdoc_valids[n->tok].pre)
return(1);
for (p = mdoc_valids[n->tok].pre; *p; p++)
if ( ! (*p)(mdoc, n))
return(0);
return(1);
}
int
mdoc_valid_post(struct mdoc *mdoc)
{
v_post *p;
if (MDOC_VALID & mdoc->last->flags)
return(1);
mdoc->last->flags |= MDOC_VALID;
if (MDOC_TEXT == mdoc->last->type)
return(1);
if (MDOC_ROOT == mdoc->last->type)
return(post_root(mdoc));
if (NULL == mdoc_valids[mdoc->last->tok].post)
return(1);
for (p = mdoc_valids[mdoc->last->tok].post; *p; p++)
if ( ! (*p)(mdoc))
return(0);
return(1);
}
static inline int
warn_count(struct mdoc *m, const char *k,
int want, const char *v, int has)
{
return(mdoc_vmsg(m, MANDOCERR_ARGCOUNT,
m->last->line, m->last->pos,
"%s %s %d (have %d)", v, k, want, has));
}
static inline int
err_count(struct mdoc *m, const char *k,
int want, const char *v, int has)
{
mdoc_vmsg(m, MANDOCERR_SYNTARGCOUNT,
m->last->line, m->last->pos,
"%s %s %d (have %d)",
v, k, want, has);
return(0);
}
/*
* Build these up with macros because they're basically the same check
* for different inequalities. Yes, this could be done with functions,
* but this is reasonable for now.
*/
#define CHECK_CHILD_DEFN(lvl, name, ineq) \
static int \
lvl##_child_##name(struct mdoc *mdoc, const char *p, int sz) \
{ \
if (mdoc->last->nchild ineq sz) \
return(1); \
return(lvl##_count(mdoc, #ineq, sz, p, mdoc->last->nchild)); \
}
#define CHECK_BODY_DEFN(name, lvl, func, num) \
static int \
b##lvl##_##name(POST_ARGS) \
{ \
if (MDOC_BODY != mdoc->last->type) \
return(1); \
return(func(mdoc, "multi-line arguments", (num))); \
}
#define CHECK_ELEM_DEFN(name, lvl, func, num) \
static int \
e##lvl##_##name(POST_ARGS) \
{ \
assert(MDOC_ELEM == mdoc->last->type); \
return(func(mdoc, "line arguments", (num))); \
}
#define CHECK_HEAD_DEFN(name, lvl, func, num) \
static int \
h##lvl##_##name(POST_ARGS) \
{ \
if (MDOC_HEAD != mdoc->last->type) \
return(1); \
return(func(mdoc, "line arguments", (num))); \
}
CHECK_CHILD_DEFN(warn, gt, >) /* warn_child_gt() */
CHECK_CHILD_DEFN(err, gt, >) /* err_child_gt() */
CHECK_CHILD_DEFN(warn, eq, ==) /* warn_child_eq() */
CHECK_CHILD_DEFN(err, eq, ==) /* err_child_eq() */
CHECK_CHILD_DEFN(err, lt, <) /* err_child_lt() */
CHECK_CHILD_DEFN(warn, lt, <) /* warn_child_lt() */
CHECK_BODY_DEFN(ge1, warn, warn_child_gt, 0) /* bwarn_ge1() */
CHECK_BODY_DEFN(ge1, err, err_child_gt, 0) /* berr_ge1() */
CHECK_ELEM_DEFN(ge1, warn, warn_child_gt, 0) /* ewarn_ge1() */
CHECK_ELEM_DEFN(eq1, err, err_child_eq, 1) /* eerr_eq1() */
CHECK_ELEM_DEFN(le1, err, err_child_lt, 2) /* eerr_le1() */
CHECK_ELEM_DEFN(eq0, err, err_child_eq, 0) /* eerr_eq0() */
CHECK_ELEM_DEFN(ge1, err, err_child_gt, 0) /* eerr_ge1() */
CHECK_HEAD_DEFN(eq0, err, err_child_eq, 0) /* herr_eq0() */
CHECK_HEAD_DEFN(le1, warn, warn_child_lt, 2) /* hwarn_le1() */
CHECK_HEAD_DEFN(ge1, err, err_child_gt, 0) /* herr_ge1() */
CHECK_HEAD_DEFN(eq1, warn, warn_child_eq, 1) /* hwarn_eq1() */
CHECK_HEAD_DEFN(eq0, warn, warn_child_eq, 0) /* hwarn_eq0() */
static int
check_stdarg(PRE_ARGS)
{
if (n->args && 1 == n->args->argc)
if (MDOC_Std == n->args->argv[0].arg)
return(1);
return(mdoc_nmsg(mdoc, n, MANDOCERR_NOARGV));
}
static int
check_args(struct mdoc *m, struct mdoc_node *n)
{
int i;
if (NULL == n->args)
return(1);
assert(n->args->argc);
for (i = 0; i < (int)n->args->argc; i++)
if ( ! check_argv(m, n, &n->args->argv[i]))
return(0);
return(1);
}
static int
check_argv(struct mdoc *m, struct mdoc_node *n, struct mdoc_argv *v)
{
int i;
for (i = 0; i < (int)v->sz; i++)
if ( ! check_text(m, v->line, v->pos, v->value[i]))
return(0);
if (MDOC_Std == v->arg) {
if (v->sz || m->meta.name)
return(1);
if ( ! mdoc_nmsg(m, n, MANDOCERR_NONAME))
return(0);
}
return(1);
}
static int
check_text(struct mdoc *mdoc, int line, int pos, char *p)
{
int c;
for ( ; *p; p++, pos++) {
if ('\t' == *p) {
if ( ! (MDOC_LITERAL & mdoc->flags))
if ( ! mdoc_pmsg(mdoc, line, pos, MANDOCERR_BADCHAR))
return(0);
} else if ( ! isprint((u_char)*p) && ASCII_HYPH != *p)
if ( ! mdoc_pmsg(mdoc, line, pos, MANDOCERR_BADCHAR))
return(0);
if ('\\' != *p)
continue;
c = mandoc_special(p);
if (c) {
p += c - 1;
pos += c - 1;
continue;
}
c = mdoc_pmsg(mdoc, line, pos, MANDOCERR_BADESCAPE);
if ( ! (MDOC_IGN_ESCAPE & mdoc->pflags) && ! c)
return(c);
}
return(1);
}
static int
check_parent(PRE_ARGS, enum mdoct tok, enum mdoc_type t)
{
assert(n->parent);
if ((MDOC_ROOT == t || tok == n->parent->tok) &&
(t == n->parent->type))
return(1);
mdoc_vmsg(mdoc, MANDOCERR_SYNTCHILD,
n->line, n->pos, "want parent %s",
MDOC_ROOT == t ? "<root>" :
mdoc_macronames[tok]);
return(0);
}
static int
pre_display(PRE_ARGS)
{
struct mdoc_node *node;
/* Display elements (`Bd', `D1'...) cannot be nested. */
if (MDOC_BLOCK != n->type)
return(1);
/* LINTED */
for (node = mdoc->last->parent; node; node = node->parent)
if (MDOC_BLOCK == node->type)
if (MDOC_Bd == node->tok)
break;
if (NULL == node)
return(1);
mdoc_nmsg(mdoc, n, MANDOCERR_NESTEDDISP);
return(0);
}
static int
pre_bl(PRE_ARGS)
{
int i, comp, dup;
const char *offs, *width;
enum mdoc_list lt;
if (MDOC_BLOCK != n->type) {
assert(n->parent);
assert(MDOC_BLOCK == n->parent->type);
assert(MDOC_Bl == n->parent->tok);
assert(LIST__NONE != n->parent->data.Bl.type);
memcpy(&n->data.Bl, &n->parent->data.Bl,
sizeof(struct mdoc_bl));
return(1);
}
/*
* First figure out which kind of list to use: bind ourselves to
* the first mentioned list type and warn about any remaining
* ones. If we find no list type, we default to LIST_item.
*/
assert(LIST__NONE == n->data.Bl.type);
/* LINTED */
for (i = 0; n->args && i < (int)n->args->argc; i++) {
lt = LIST__NONE;
dup = comp = 0;
width = offs = NULL;
switch (n->args->argv[i].arg) {
/* Set list types. */
case (MDOC_Bullet):
lt = LIST_bullet;
break;
case (MDOC_Dash):
lt = LIST_dash;
break;
case (MDOC_Enum):
lt = LIST_enum;
break;
case (MDOC_Hyphen):
lt = LIST_hyphen;
break;
case (MDOC_Item):
lt = LIST_item;
break;
case (MDOC_Tag):
lt = LIST_tag;
break;
case (MDOC_Diag):
lt = LIST_diag;
break;
case (MDOC_Hang):
lt = LIST_hang;
break;
case (MDOC_Ohang):
lt = LIST_ohang;
break;
case (MDOC_Inset):
lt = LIST_inset;
break;
case (MDOC_Column):
lt = LIST_column;
break;
/* Set list arguments. */
case (MDOC_Compact):
dup = n->data.Bl.comp;
comp = 1;
break;
case (MDOC_Width):
dup = (NULL != n->data.Bl.width);
width = n->args->argv[i].value[0];
break;
case (MDOC_Offset):
/* NB: this can be empty! */
if (n->args->argv[i].sz) {
offs = n->args->argv[i].value[0];
dup = (NULL != n->data.Bl.offs);
break;
}
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV))
return(0);
break;
}
/* Check: duplicate auxiliary arguments. */
if (dup && ! mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP))
return(0);
if (comp && ! dup)
n->data.Bl.comp = comp;
if (offs && ! dup)
n->data.Bl.offs = offs;
if (width && ! dup)
n->data.Bl.width = width;
/* Check: multiple list types. */
if (LIST__NONE != lt && n->data.Bl.type != LIST__NONE)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_LISTREP))
return(0);
/* Assign list type. */
if (LIST__NONE != lt && n->data.Bl.type == LIST__NONE)
n->data.Bl.type = lt;
/* The list type should come first. */
if (n->data.Bl.type == LIST__NONE)
if (n->data.Bl.width ||
n->data.Bl.offs ||
n->data.Bl.comp)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_LISTFIRST))
return(0);
continue;
}
/* Allow lists to default to LIST_item. */
if (LIST__NONE == n->data.Bl.type) {
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_LISTTYPE))
return(0);
n->data.Bl.type = LIST_item;
}
/*
* Validate the width field. Some list types don't need width
* types and should be warned about them. Others should have it
* and must also be warned.
*/
switch (n->data.Bl.type) {
case (LIST_tag):
if (n->data.Bl.width)
break;
if (mdoc_nmsg(mdoc, n, MANDOCERR_NOWIDTHARG))
break;
return(0);
case (LIST_column):
/* FALLTHROUGH */
case (LIST_diag):
/* FALLTHROUGH */
case (LIST_ohang):
/* FALLTHROUGH */
case (LIST_inset):
/* FALLTHROUGH */
case (LIST_item):
if (NULL == n->data.Bl.width)
break;
if (mdoc_nmsg(mdoc, n, MANDOCERR_WIDTHARG))
break;
return(0);
default:
break;
}
return(1);
}
static int
pre_bd(PRE_ARGS)
{
int i, dup, comp;
enum mdoc_disp dt;
const char *offs;
if (MDOC_BLOCK != n->type) {
assert(n->parent);
assert(MDOC_BLOCK == n->parent->type);
assert(MDOC_Bd == n->parent->tok);
assert(DISP__NONE != n->parent->data.Bd.type);
memcpy(&n->data.Bd, &n->parent->data.Bd,
sizeof(struct mdoc_bd));
return(1);
}
assert(DISP__NONE == n->data.Bd.type);
/* LINTED */
for (i = 0; n->args && i < (int)n->args->argc; i++) {
dt = DISP__NONE;
dup = comp = 0;
offs = NULL;
switch (n->args->argv[i].arg) {
case (MDOC_Centred):
dt = DISP_centred;
break;
case (MDOC_Ragged):
dt = DISP_ragged;
break;
case (MDOC_Unfilled):
dt = DISP_unfilled;
break;
case (MDOC_Filled):
dt = DISP_filled;
break;
case (MDOC_Literal):
dt = DISP_literal;
break;
case (MDOC_File):
mdoc_nmsg(mdoc, n, MANDOCERR_BADDISP);
return(0);
case (MDOC_Offset):
/* NB: this can be empty! */
if (n->args->argv[i].sz) {
offs = n->args->argv[i].value[0];
dup = (NULL != n->data.Bd.offs);
break;
}
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_IGNARGV))
return(0);
break;
case (MDOC_Compact):
comp = 1;
dup = n->data.Bd.comp;
break;
default:
abort();
/* NOTREACHED */
}
/* Check whether we have duplicates. */
if (dup && ! mdoc_nmsg(mdoc, n, MANDOCERR_ARGVREP))
return(0);
/* Make our auxiliary assignments. */
if (offs && ! dup)
n->data.Bd.offs = offs;
if (comp && ! dup)
n->data.Bd.comp = comp;
/* Check whether a type has already been assigned. */
if (DISP__NONE != dt && n->data.Bd.type != DISP__NONE)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_DISPREP))
return(0);
/* Make our type assignment. */
if (DISP__NONE != dt && n->data.Bd.type == DISP__NONE)
n->data.Bd.type = dt;
}
if (DISP__NONE == n->data.Bd.type) {
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_DISPTYPE))
return(0);
n->data.Bd.type = DISP_ragged;
}
return(1);
}
static int
pre_ss(PRE_ARGS)
{
if (MDOC_BLOCK != n->type)
return(1);
return(check_parent(mdoc, n, MDOC_Sh, MDOC_BODY));
}
static int
pre_sh(PRE_ARGS)
{
if (MDOC_BLOCK != n->type)
return(1);
mdoc->regs->regs[(int)REG_nS].set = 0;
return(check_parent(mdoc, n, MDOC_MAX, MDOC_ROOT));
}
static int
pre_it(PRE_ARGS)
{
if (MDOC_BLOCK != n->type)
return(1);
/*
* FIXME: this can probably be lifted if we make the It into
* something else on-the-fly?
*/
return(check_parent(mdoc, n, MDOC_Bl, MDOC_BODY));
}
static int
pre_an(PRE_ARGS)
{
if (NULL == n->args || 1 == n->args->argc)
return(1);
mdoc_vmsg(mdoc, MANDOCERR_SYNTARGCOUNT,
n->line, n->pos,
"line arguments == 1 (have %d)",
n->args->argc);
return(0);
}
static int
pre_rv(PRE_ARGS)
{
return(check_stdarg(mdoc, n));
}
static int
post_dt(POST_ARGS)
{
const struct mdoc_node *nn;
const char *p;
if (NULL != (nn = mdoc->last->child))
for (p = nn->string; *p; p++) {
if (toupper((u_char)*p) == *p)
continue;
if ( ! mdoc_nmsg(mdoc, nn, MANDOCERR_UPPERCASE))
return(0);
break;
}
return(1);
}
static int
pre_dt(PRE_ARGS)
{
if (0 == mdoc->meta.date || mdoc->meta.os)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO))
return(0);
if (mdoc->meta.title)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP))
return(0);
return(1);
}
static int
pre_os(PRE_ARGS)
{
if (NULL == mdoc->meta.title || 0 == mdoc->meta.date)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO))
return(0);
if (mdoc->meta.os)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP))
return(0);
return(1);
}
static int
pre_dd(PRE_ARGS)
{
if (mdoc->meta.title || mdoc->meta.os)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGOOO))
return(0);
if (mdoc->meta.date)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_PROLOGREP))
return(0);
return(1);
}
static int
post_bf(POST_ARGS)
{
char *p;
struct mdoc_node *head;
if (MDOC_BLOCK != mdoc->last->type)
return(1);
head = mdoc->last->head;
if (mdoc->last->args && head->child) {
/* FIXME: this should provide a default. */
mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SYNTARGVCOUNT);
return(0);
} else if (mdoc->last->args)
return(1);
if (NULL == head->child || MDOC_TEXT != head->child->type) {
/* FIXME: this should provide a default. */
mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SYNTARGVCOUNT);
return(0);
}
p = head->child->string;
if (0 == strcmp(p, "Em"))
return(1);
else if (0 == strcmp(p, "Li"))
return(1);
else if (0 == strcmp(p, "Sy"))
return(1);
mdoc_nmsg(mdoc, head, MANDOCERR_FONTTYPE);
return(0);
}
static int
post_lb(POST_ARGS)
{
if (mdoc_a2lib(mdoc->last->child->string))
return(1);
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADLIB));
}
static int
post_eoln(POST_ARGS)
{
if (NULL == mdoc->last->child)
return(1);
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST));
}
static int
post_vt(POST_ARGS)
{
const struct mdoc_node *n;
/*
* The Vt macro comes in both ELEM and BLOCK form, both of which
* have different syntaxes (yet more context-sensitive
* behaviour). ELEM types must have a child; BLOCK types,
* specifically the BODY, should only have TEXT children.
*/
if (MDOC_ELEM == mdoc->last->type)
return(eerr_ge1(mdoc));
if (MDOC_BODY != mdoc->last->type)
return(1);
for (n = mdoc->last->child; n; n = n->next)
if (MDOC_TEXT != n->type)
if ( ! mdoc_nmsg(mdoc, n, MANDOCERR_CHILD))
return(0);
return(1);
}
static int
post_nm(POST_ARGS)
{
if (mdoc->last->child)
return(1);
if (mdoc->meta.name)
return(1);
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NONAME));
}
static int
post_at(POST_ARGS)
{
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));
}
static int
post_an(POST_ARGS)
{
if (mdoc->last->args) {
if (NULL == mdoc->last->child)
return(1);
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGCOUNT));
}
if (mdoc->last->child)
return(1);
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS));
}
static int
post_it(POST_ARGS)
{
int i, cols, rc;
enum mdoc_list lt;
struct mdoc_node *n, *c;
enum mandocerr er;
if (MDOC_BLOCK != mdoc->last->type)
return(1);
n = mdoc->last->parent->parent;
lt = n->data.Bl.type;
if (LIST__NONE == lt) {
mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_LISTTYPE);
return(0);
}
switch (lt) {
case (LIST_tag):
if (mdoc->last->head->child)
break;
/* FIXME: give this a dummy value. */
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS))
return(0);
break;
case (LIST_hang):
/* FALLTHROUGH */
case (LIST_ohang):
/* FALLTHROUGH */
case (LIST_inset):
/* FALLTHROUGH */
case (LIST_diag):
if (NULL == mdoc->last->head->child)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOARGS))
return(0);
if (NULL == mdoc->last->body->child)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY))
return(0);
break;
case (LIST_bullet):
/* FALLTHROUGH */
case (LIST_dash):
/* FALLTHROUGH */
case (LIST_enum):
/* FALLTHROUGH */
case (LIST_hyphen):
/* FALLTHROUGH */
case (LIST_item):
if (mdoc->last->head->child)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_ARGSLOST))
return(0);
if (NULL == mdoc->last->body->child)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY))
return(0);
break;
case (LIST_column):
cols = -1;
for (i = 0; i < (int)n->args->argc; i++)
if (MDOC_Column == n->args->argv[i].arg) {
cols = (int)n->args->argv[i].sz;
break;
}
assert(-1 != cols);
assert(NULL == mdoc->last->head->child);
if (NULL == mdoc->last->body->child)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NOBODY))
return(0);
for (i = 0, c = mdoc->last->child; c; c = c->next)
if (MDOC_BODY == c->type)
i++;
if (i < cols)
er = MANDOCERR_ARGCOUNT;
else if (i == cols || i == cols + 1)
break;
else
er = MANDOCERR_SYNTARGCOUNT;
rc = mdoc_vmsg(mdoc, er,
mdoc->last->line, mdoc->last->pos,
"columns == %d (have %d)", cols, i);
return(rc);
default:
break;
}
return(1);
}
static int
post_bl_head(POST_ARGS)
{
int i;
struct mdoc_node *n;
assert(mdoc->last->parent);
n = mdoc->last->parent;
if (LIST_column == n->data.Bl.type) {
for (i = 0; i < (int)n->args->argc; i++)
if (MDOC_Column == n->args->argv[i].arg)
break;
assert(i < (int)n->args->argc);
if (n->args->argv[i].sz && mdoc->last->nchild) {
mdoc_nmsg(mdoc, n, MANDOCERR_COLUMNS);
return(0);
}
return(1);
}
if (0 == (i = mdoc->last->nchild))
return(1);
return(warn_count(mdoc, "==", 0, "line arguments", i));
}
static int
post_bl(POST_ARGS)
{
struct mdoc_node *n;
if (MDOC_HEAD == mdoc->last->type)
return(post_bl_head(mdoc));
if (MDOC_BODY != mdoc->last->type)
return(1);
if (NULL == mdoc->last->child)
return(1);
/*
* We only allow certain children of `Bl'. This is usually on
* `It', but apparently `Sm' occurs here and there, so we let
* that one through, too.
*/
/* LINTED */
for (n = mdoc->last->child; n; n = n->next) {
if (MDOC_BLOCK == n->type && MDOC_It == n->tok)
continue;
if (MDOC_Sm == n->tok)
continue;
mdoc_nmsg(mdoc, n, MANDOCERR_SYNTCHILD);
return(0);
}
return(1);
}
static int
ebool(struct mdoc *mdoc)
{
struct mdoc_node *n;
/* LINTED */
for (n = mdoc->last->child; n; n = n->next) {
if (MDOC_TEXT != n->type)
break;
if (0 == strcmp(n->string, "on"))
continue;
if (0 == strcmp(n->string, "off"))
continue;
break;
}
if (NULL == n)
return(1);
return(mdoc_nmsg(mdoc, n, MANDOCERR_BADBOOL));
}
static int
post_root(POST_ARGS)
{
if (NULL == mdoc->first->child)
mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCBODY);
else if ( ! (MDOC_PBODY & mdoc->flags))
mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCPROLOG);
else if (MDOC_BLOCK != mdoc->first->child->type)
mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCBODY);
else if (MDOC_Sh != mdoc->first->child->tok)
mdoc_nmsg(mdoc, mdoc->first, MANDOCERR_NODOCBODY);
else
return(1);
return(0);
}
static int
post_st(POST_ARGS)
{
if (mdoc_a2st(mdoc->last->child->string))
return(1);
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADSTANDARD));
}
static int
post_rs(POST_ARGS)
{
struct mdoc_node *nn;
if (MDOC_BODY != mdoc->last->type)
return(1);
for (nn = mdoc->last->child; nn; nn = nn->next)
switch (nn->tok) {
case(MDOC__U):
/* FALLTHROUGH */
case(MDOC__Q):
/* FALLTHROUGH */
case(MDOC__C):
/* FALLTHROUGH */
case(MDOC__A):
/* FALLTHROUGH */
case(MDOC__B):
/* FALLTHROUGH */
case(MDOC__D):
/* FALLTHROUGH */
case(MDOC__I):
/* FALLTHROUGH */
case(MDOC__J):
/* FALLTHROUGH */
case(MDOC__N):
/* FALLTHROUGH */
case(MDOC__O):
/* FALLTHROUGH */
case(MDOC__P):
/* FALLTHROUGH */
case(MDOC__R):
/* FALLTHROUGH */
case(MDOC__T):
/* FALLTHROUGH */
case(MDOC__V):
break;
default:
mdoc_nmsg(mdoc, nn, MANDOCERR_SYNTCHILD);
return(0);
}
return(1);
}
static int
post_sh(POST_ARGS)
{
if (MDOC_HEAD == mdoc->last->type)
return(post_sh_head(mdoc));
if (MDOC_BODY == mdoc->last->type)
return(post_sh_body(mdoc));
return(1);
}
static int
post_sh_body(POST_ARGS)
{
struct mdoc_node *n;
if (SEC_NAME != mdoc->lastsec)
return(1);
/*
* Warn if the NAME section doesn't contain the `Nm' and `Nd'
* macros (can have multiple `Nm' and one `Nd'). Note that the
* children of the BODY declaration can also be "text".
*/
if (NULL == (n = mdoc->last->child))
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC));
for ( ; n && n->next; n = n->next) {
if (MDOC_ELEM == n->type && MDOC_Nm == n->tok)
continue;
if (MDOC_TEXT == n->type)
continue;
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC))
return(0);
}
assert(n);
if (MDOC_BLOCK == n->type && MDOC_Nd == n->tok)
return(1);
return(mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_BADNAMESEC));
}
static int
post_sh_head(POST_ARGS)
{
char buf[BUFSIZ];
enum mdoc_sec sec;
const 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.
*/
buf[0] = '\0';
/*
* FIXME: yes, these can use a dynamic buffer, but I don't do so
* in the interests of simplicity.
*/
for (n = mdoc->last->child; n; n = n->next) {
/* XXX - copied from compact(). */
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);
}
}
sec = mdoc_str2sec(buf);
/*
* Check: NAME should always be first, CUSTOM has no roles,
* non-CUSTOM has a conventional order to be followed.
*/
if (SEC_NAME != sec && SEC_NONE == mdoc->lastnamed)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_NAMESECFIRST))
return(0);
if (SEC_CUSTOM == sec)
return(1);
if (sec == mdoc->lastnamed)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECREP))
return(0);
if (sec < mdoc->lastnamed)
if ( ! mdoc_nmsg(mdoc, mdoc->last, MANDOCERR_SECOOO))
return(0);
/*
* Check particular section/manual conventions. LIBRARY can
* only occur in manual section 2, 3, and 9.
*/
switch (sec) {
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));
default:
break;
}
return(1);
}