Return to mdoc.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.79, 2009/06/15 10:02:53 | version 1.176, 2011/01/01 12:59:17 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se> | * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2010 Ingo Schwarze <schwarze@openbsd.org> | |||
* | * | ||
* Permission to use, copy, modify, and distribute this software for any | * Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | * purpose with or without fee is hereby granted, provided that the above | ||
|
|
||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*/ | */ | ||
#ifdef HAVE_CONFIG_H | |||
#include "config.h" | |||
#endif | |||
#include <sys/types.h> | |||
#include <assert.h> | #include <assert.h> | ||
#include <ctype.h> | |||
#include <stdarg.h> | #include <stdarg.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <string.h> | #include <string.h> | ||
#include <time.h> | |||
#include "mandoc.h" | |||
#include "libmdoc.h" | #include "libmdoc.h" | ||
#include "libmandoc.h" | |||
enum merr { | |||
ENOCALL, | |||
EBODYPROL, | |||
EPROLBODY, | |||
ESPACE, | |||
ETEXTPROL, | |||
ENOBLANK, | |||
EMALLOC | |||
}; | |||
const char *const __mdoc_macronames[MDOC_MAX] = { | const char *const __mdoc_macronames[MDOC_MAX] = { | ||
"\\\"", "Dd", "Dt", "Os", | "Ap", "Dd", "Dt", "Os", | ||
"Sh", "Ss", "Pp", "D1", | "Sh", "Ss", "Pp", "D1", | ||
"Dl", "Bd", "Ed", "Bl", | "Dl", "Bd", "Ed", "Bl", | ||
"El", "It", "Ad", "An", | "El", "It", "Ad", "An", | ||
|
|
||
"Nm", "Op", "Ot", "Pa", | "Nm", "Op", "Ot", "Pa", | ||
"Rv", "St", "Va", "Vt", | "Rv", "St", "Va", "Vt", | ||
/* LINTED */ | /* LINTED */ | ||
"Xr", "\%A", "\%B", "\%D", | "Xr", "%A", "%B", "%D", | ||
/* LINTED */ | /* LINTED */ | ||
"\%I", "\%J", "\%N", "\%O", | "%I", "%J", "%N", "%O", | ||
/* LINTED */ | /* LINTED */ | ||
"\%P", "\%R", "\%T", "\%V", | "%P", "%R", "%T", "%V", | ||
"Ac", "Ao", "Aq", "At", | "Ac", "Ao", "Aq", "At", | ||
"Bc", "Bf", "Bo", "Bq", | "Bc", "Bf", "Bo", "Bq", | ||
"Bsx", "Bx", "Db", "Dc", | "Bsx", "Bx", "Db", "Dc", | ||
|
|
||
"Tn", "Ux", "Xc", "Xo", | "Tn", "Ux", "Xc", "Xo", | ||
"Fo", "Fc", "Oo", "Oc", | "Fo", "Fc", "Oo", "Oc", | ||
"Bk", "Ek", "Bt", "Hf", | "Bk", "Ek", "Bt", "Hf", | ||
"Fr", "Ud", "Lb", "Ap", | "Fr", "Ud", "Lb", "Lp", | ||
"Lp", "Lk", "Mt", "Brq", | "Lk", "Mt", "Brq", "Bro", | ||
/* LINTED */ | /* LINTED */ | ||
"Bro", "Brc", "\%C", "Es", | "Brc", "%C", "Es", "En", | ||
/* LINTED */ | /* LINTED */ | ||
"En", "Dx", "\%Q" | "Dx", "%Q", "br", "sp", | ||
/* LINTED */ | |||
"%U", "Ta" | |||
}; | }; | ||
const char *const __mdoc_argnames[MDOC_ARG_MAX] = { | const char *const __mdoc_argnames[MDOC_ARG_MAX] = { | ||
|
|
||
"ohang", "inset", "column", | "ohang", "inset", "column", | ||
"width", "compact", "std", | "width", "compact", "std", | ||
"filled", "words", "emphasis", | "filled", "words", "emphasis", | ||
"symbolic", "nested" | "symbolic", "nested", "centered" | ||
}; | }; | ||
const char * const *mdoc_macronames = __mdoc_macronames; | const char * const *mdoc_macronames = __mdoc_macronames; | ||
const char * const *mdoc_argnames = __mdoc_argnames; | const char * const *mdoc_argnames = __mdoc_argnames; | ||
static void mdoc_node_free(struct mdoc_node *); | |||
static void mdoc_node_unlink(struct mdoc *, | |||
struct mdoc_node *); | |||
static void mdoc_free1(struct mdoc *); | static void mdoc_free1(struct mdoc *); | ||
static int mdoc_alloc1(struct mdoc *); | static void mdoc_alloc1(struct mdoc *); | ||
static struct mdoc_node *node_alloc(struct mdoc *, int, int, | static struct mdoc_node *node_alloc(struct mdoc *, int, int, | ||
int, enum mdoc_type); | enum mdoct, enum mdoc_type); | ||
static int node_append(struct mdoc *, | static int node_append(struct mdoc *, | ||
struct mdoc_node *); | struct mdoc_node *); | ||
static int parsetext(struct mdoc *, int, char *); | static int mdoc_ptext(struct mdoc *, int, char *, int); | ||
static int parsemacro(struct mdoc *, int, char *); | static int mdoc_pmacro(struct mdoc *, int, char *, int); | ||
static int macrowarn(struct mdoc *, int, const char *); | static int mdoc_span_alloc(struct mdoc *, | ||
static int perr(struct mdoc *, int, int, enum merr); | const struct tbl_span *); | ||
#define verr(m, t) perr((m), (m)->last->line, (m)->last->pos, (t)) | |||
/* | |||
* Get the first (root) node of the parse tree. | |||
*/ | |||
const struct mdoc_node * | const struct mdoc_node * | ||
mdoc_node(const struct mdoc *m) | mdoc_node(const struct mdoc *m) | ||
{ | { | ||
|
|
||
} | } | ||
/* | |||
* Frees volatile resources (parse tree, meta-data, fields). | |||
*/ | |||
static void | static void | ||
mdoc_free1(struct mdoc *mdoc) | mdoc_free1(struct mdoc *mdoc) | ||
{ | { | ||
if (mdoc->first) | if (mdoc->first) | ||
mdoc_node_freelist(mdoc->first); | mdoc_node_delete(mdoc, mdoc->first); | ||
if (mdoc->meta.title) | if (mdoc->meta.title) | ||
free(mdoc->meta.title); | free(mdoc->meta.title); | ||
if (mdoc->meta.os) | if (mdoc->meta.os) | ||
|
|
||
free(mdoc->meta.arch); | free(mdoc->meta.arch); | ||
if (mdoc->meta.vol) | if (mdoc->meta.vol) | ||
free(mdoc->meta.vol); | free(mdoc->meta.vol); | ||
if (mdoc->meta.msec) | |||
free(mdoc->meta.msec); | |||
} | } | ||
static int | /* | ||
* Allocate all volatile resources (parse tree, meta-data, fields). | |||
*/ | |||
static void | |||
mdoc_alloc1(struct mdoc *mdoc) | mdoc_alloc1(struct mdoc *mdoc) | ||
{ | { | ||
bzero(&mdoc->meta, sizeof(struct mdoc_meta)); | memset(&mdoc->meta, 0, sizeof(struct mdoc_meta)); | ||
mdoc->flags = 0; | mdoc->flags = 0; | ||
mdoc->lastnamed = mdoc->lastsec = 0; | mdoc->lastnamed = mdoc->lastsec = SEC_NONE; | ||
mdoc->last = calloc(1, sizeof(struct mdoc_node)); | mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node)); | ||
if (NULL == mdoc->last) | |||
return(0); | |||
mdoc->first = mdoc->last; | mdoc->first = mdoc->last; | ||
mdoc->last->type = MDOC_ROOT; | mdoc->last->type = MDOC_ROOT; | ||
mdoc->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
return(1); | |||
} | } | ||
/* | /* | ||
* Free up all resources contributed by a parse: the node tree, | * Free up volatile resources (see mdoc_free1()) then re-initialises the | ||
* meta-data and so on. Then reallocate the root node for another | * data with mdoc_alloc1(). After invocation, parse data has been reset | ||
* parse. | * and the parser is ready for re-invocation on a new tree; however, | ||
* cross-parse non-volatile data is kept intact. | |||
*/ | */ | ||
int | void | ||
mdoc_reset(struct mdoc *mdoc) | mdoc_reset(struct mdoc *mdoc) | ||
{ | { | ||
mdoc_free1(mdoc); | mdoc_free1(mdoc); | ||
return(mdoc_alloc1(mdoc)); | mdoc_alloc1(mdoc); | ||
} | } | ||
/* | /* | ||
* Completely free up all resources. | * Completely free up all volatile and non-volatile parse resources. | ||
* After invocation, the pointer is no longer usable. | |||
*/ | */ | ||
void | void | ||
mdoc_free(struct mdoc *mdoc) | mdoc_free(struct mdoc *mdoc) | ||
{ | { | ||
mdoc_free1(mdoc); | mdoc_free1(mdoc); | ||
if (mdoc->htab) | |||
mdoc_hash_free(mdoc->htab); | |||
free(mdoc); | free(mdoc); | ||
} | } | ||
/* | |||
* Allocate volatile and non-volatile parse resources. | |||
*/ | |||
struct mdoc * | struct mdoc * | ||
mdoc_alloc(void *data, int pflags, const struct mdoc_cb *cb) | mdoc_alloc(struct regset *regs, void *data, mandocmsg msg) | ||
{ | { | ||
struct mdoc *p; | struct mdoc *p; | ||
if (NULL == (p = calloc(1, sizeof(struct mdoc)))) | p = mandoc_calloc(1, sizeof(struct mdoc)); | ||
return(NULL); | |||
if (cb) | |||
(void)memcpy(&p->cb, cb, sizeof(struct mdoc_cb)); | |||
p->msg = msg; | |||
p->data = data; | p->data = data; | ||
p->pflags = pflags; | p->regs = regs; | ||
if (NULL == (p->htab = mdoc_hash_alloc())) { | mdoc_hash_init(); | ||
free(p); | mdoc_alloc1(p); | ||
return(NULL); | return(p); | ||
} else if (mdoc_alloc1(p)) | |||
return(p); | |||
free(p); | |||
return(NULL); | |||
} | } | ||
/* | /* | ||
* Climb back up the parse tree, validating open scopes. Mostly calls | * Climb back up the parse tree, validating open scopes. Mostly calls | ||
* through to macro_end in macro.c. | * through to macro_end() in macro.c. | ||
*/ | */ | ||
int | int | ||
mdoc_endparse(struct mdoc *m) | mdoc_endparse(struct mdoc *m) | ||
|
|
||
return(0); | return(0); | ||
} | } | ||
/* | |||
* Main parse routine. Parses a single line -- really just hands off to | |||
* the macro or text parser. | |||
*/ | |||
int | int | ||
mdoc_parseln(struct mdoc *m, int ln, char *buf) | mdoc_addspan(struct mdoc *m, const struct tbl_span *sp) | ||
{ | { | ||
/* If in error-mode, then we parse no more. */ | |||
if (MDOC_HALT & m->flags) | if (MDOC_HALT & m->flags) | ||
return(0); | return(0); | ||
return('.' == *buf ? parsemacro(m, ln, buf) : | /* No text before an initial macro. */ | ||
parsetext(m, ln, buf)); | |||
} | |||
if (SEC_NONE == m->lastnamed) { | |||
/* FIXME: grab from span. */ | |||
mdoc_pmsg(m, 0, 0, MANDOCERR_NOTEXT); | |||
return(1); | |||
} | |||
void | return(mdoc_span_alloc(m, sp)); | ||
mdoc_vmsg(struct mdoc *mdoc, int ln, int pos, const char *fmt, ...) | |||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_msg) | |||
return; | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
(*mdoc->cb.mdoc_msg)(mdoc->data, ln, pos, buf); | |||
} | } | ||
/* | |||
* Main parse routine. Parses a single line -- really just hands off to | |||
* the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). | |||
*/ | |||
int | int | ||
mdoc_verr(struct mdoc *mdoc, int ln, int pos, | mdoc_parseln(struct mdoc *m, int ln, char *buf, int offs) | ||
const char *fmt, ...) | |||
{ | { | ||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_err) | if (MDOC_HALT & m->flags) | ||
return(0); | return(0); | ||
va_start(ap, fmt); | m->flags |= MDOC_NEWLINE; | ||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
return((*mdoc->cb.mdoc_err)(mdoc->data, ln, pos, buf)); | |||
} | |||
/* | |||
* 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 (m->regs->regs[(int)REG_nS].set) { | |||
if (m->regs->regs[(int)REG_nS].v.u) | |||
m->flags |= MDOC_SYNOPSIS; | |||
else | |||
m->flags &= ~MDOC_SYNOPSIS; | |||
} | |||
int | return(('.' == buf[offs] || '\'' == buf[offs]) ? | ||
mdoc_vwarn(struct mdoc *mdoc, int ln, int pos, | mdoc_pmacro(m, ln, buf, offs) : | ||
enum mdoc_warn type, const char *fmt, ...) | mdoc_ptext(m, ln, buf, offs)); | ||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_warn) | |||
return(0); | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
return((*mdoc->cb.mdoc_warn)(mdoc->data, ln, pos, type, buf)); | |||
} | } | ||
int | int | ||
mdoc_nerr(struct mdoc *mdoc, const struct mdoc_node *node, const char *fmt, ...) | mdoc_vmsg(struct mdoc *mdoc, enum mandocerr t, | ||
int ln, int pos, const char *fmt, ...) | |||
{ | { | ||
char buf[256]; | char buf[256]; | ||
va_list ap; | va_list ap; | ||
if (NULL == mdoc->cb.mdoc_err) | |||
return(0); | |||
va_start(ap, fmt); | va_start(ap, fmt); | ||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | ||
va_end(ap); | va_end(ap); | ||
return((*mdoc->cb.mdoc_err)(mdoc->data, node->line, node->pos, buf)); | |||
} | |||
return((*mdoc->msg)(t, mdoc->data, ln, pos, buf)); | |||
int | |||
mdoc_warn(struct mdoc *mdoc, enum mdoc_warn type, const char *fmt, ...) | |||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_warn) | |||
return(0); | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
return((*mdoc->cb.mdoc_warn)(mdoc->data, mdoc->last->line, | |||
mdoc->last->pos, type, buf)); | |||
} | } | ||
int | int | ||
mdoc_err(struct mdoc *mdoc, const char *fmt, ...) | mdoc_macro(MACRO_PROT_ARGS) | ||
{ | { | ||
char buf[256]; | assert(tok < MDOC_MAX); | ||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_err) | /* If we're in the body, deny prologue calls. */ | ||
return(0); | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
return((*mdoc->cb.mdoc_err)(mdoc->data, mdoc->last->line, | |||
mdoc->last->pos, buf)); | |||
} | |||
void | |||
mdoc_msg(struct mdoc *mdoc, const char *fmt, ...) | |||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_msg) | |||
return; | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
(*mdoc->cb.mdoc_msg)(mdoc->data, mdoc->last->line, mdoc->last->pos, | |||
buf); | |||
} | |||
void | |||
mdoc_pmsg(struct mdoc *mdoc, int line, int pos, const char *fmt, ...) | |||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_msg) | |||
return; | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
(*mdoc->cb.mdoc_msg)(mdoc->data, line, pos, buf); | |||
} | |||
int | |||
mdoc_pwarn(struct mdoc *mdoc, int line, int pos, enum mdoc_warn type, | |||
const char *fmt, ...) | |||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_warn) | |||
return(0); | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
return((*mdoc->cb.mdoc_warn)(mdoc->data, line, pos, type, buf)); | |||
} | |||
int | |||
mdoc_perr(struct mdoc *mdoc, int line, int pos, const char *fmt, ...) | |||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_err) | |||
return(0); | |||
va_start(ap, fmt); | |||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
return((*mdoc->cb.mdoc_err)(mdoc->data, line, pos, buf)); | |||
} | |||
int | |||
mdoc_macro(struct mdoc *m, int tok, | |||
int ln, int pp, int *pos, char *buf) | |||
{ | |||
/* FIXME - these should happen during validation. */ | |||
if (MDOC_PROLOGUE & mdoc_macros[tok].flags && | if (MDOC_PROLOGUE & mdoc_macros[tok].flags && | ||
SEC_PROLOGUE != m->lastnamed) | MDOC_PBODY & m->flags) { | ||
return(perr(m, ln, pp, EPROLBODY)); | mdoc_pmsg(m, line, ppos, MANDOCERR_BADBODY); | ||
return(1); | |||
} | |||
/* If we're in the prologue, deny "body" macros. */ | |||
if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && | if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && | ||
SEC_PROLOGUE == m->lastnamed) | ! (MDOC_PBODY & m->flags)) { | ||
return(perr(m, ln, pp, EBODYPROL)); | mdoc_pmsg(m, line, ppos, MANDOCERR_BADPROLOG); | ||
if (NULL == m->meta.msec) | |||
m->meta.msec = mandoc_strdup("1"); | |||
if (NULL == m->meta.title) | |||
m->meta.title = mandoc_strdup("UNKNOWN"); | |||
if (NULL == m->meta.vol) | |||
m->meta.vol = mandoc_strdup("LOCAL"); | |||
if (NULL == m->meta.os) | |||
m->meta.os = mandoc_strdup("LOCAL"); | |||
if (0 == m->meta.date) | |||
m->meta.date = time(NULL); | |||
m->flags |= MDOC_PBODY; | |||
} | |||
if (1 != pp && ! (MDOC_CALLABLE & mdoc_macros[tok].flags)) | return((*mdoc_macros[tok].fp)(m, tok, line, ppos, pos, buf)); | ||
return(perr(m, ln, pp, ENOCALL)); | |||
return((*mdoc_macros[tok].fp)(m, tok, ln, pp, pos, buf)); | |||
} | } | ||
static int | static int | ||
perr(struct mdoc *m, int line, int pos, enum merr type) | |||
{ | |||
char *p; | |||
p = NULL; | |||
switch (type) { | |||
case (ENOCALL): | |||
p = "not callable"; | |||
break; | |||
case (EPROLBODY): | |||
p = "macro disallowed in document body"; | |||
break; | |||
case (EBODYPROL): | |||
p = "macro disallowed in document prologue"; | |||
break; | |||
case (EMALLOC): | |||
p = "memory exhausted"; | |||
break; | |||
case (ETEXTPROL): | |||
p = "text disallowed in document prologue"; | |||
break; | |||
case (ENOBLANK): | |||
p = "blank lines disallowed in non-literal contexts"; | |||
break; | |||
case (ESPACE): | |||
p = "whitespace disallowed after delimiter"; | |||
break; | |||
} | |||
assert(p); | |||
return(mdoc_perr(m, line, pos, p)); | |||
} | |||
static int | |||
node_append(struct mdoc *mdoc, struct mdoc_node *p) | node_append(struct mdoc *mdoc, struct mdoc_node *p) | ||
{ | { | ||
|
|
||
/* NOTREACHED */ | /* NOTREACHED */ | ||
} | } | ||
p->parent->nchild++; | |||
/* | |||
* Copy over the normalised-data pointer of our parent. Not | |||
* everybody has one, but copying a null pointer is fine. | |||
*/ | |||
switch (p->type) { | |||
case (MDOC_BODY): | |||
/* FALLTHROUGH */ | |||
case (MDOC_TAIL): | |||
/* FALLTHROUGH */ | |||
case (MDOC_HEAD): | |||
p->norm = p->parent->norm; | |||
break; | |||
default: | |||
break; | |||
} | |||
if ( ! mdoc_valid_pre(mdoc, p)) | if ( ! mdoc_valid_pre(mdoc, p)) | ||
return(0); | return(0); | ||
if ( ! mdoc_action_pre(mdoc, p)) | |||
return(0); | |||
switch (p->type) { | switch (p->type) { | ||
case (MDOC_HEAD): | case (MDOC_HEAD): | ||
|
|
||
p->parent->tail = p; | p->parent->tail = p; | ||
break; | break; | ||
case (MDOC_BODY): | case (MDOC_BODY): | ||
if (p->end) | |||
break; | |||
assert(MDOC_BLOCK == p->parent->type); | assert(MDOC_BLOCK == p->parent->type); | ||
p->parent->body = p; | p->parent->body = p; | ||
break; | break; | ||
|
|
||
mdoc->last = p; | mdoc->last = p; | ||
switch (p->type) { | switch (p->type) { | ||
case (MDOC_TBL): | |||
/* FALLTHROUGH */ | |||
case (MDOC_TEXT): | case (MDOC_TEXT): | ||
if ( ! mdoc_valid_post(mdoc)) | if ( ! mdoc_valid_post(mdoc)) | ||
return(0); | return(0); | ||
if ( ! mdoc_action_post(mdoc)) | |||
return(0); | |||
break; | break; | ||
default: | default: | ||
break; | break; | ||
|
|
||
static struct mdoc_node * | static struct mdoc_node * | ||
node_alloc(struct mdoc *mdoc, int line, | node_alloc(struct mdoc *m, int line, int pos, | ||
int pos, int tok, enum mdoc_type type) | enum mdoct tok, enum mdoc_type type) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
if (NULL == (p = calloc(1, sizeof(struct mdoc_node)))) { | p = mandoc_calloc(1, sizeof(struct mdoc_node)); | ||
(void)verr(mdoc, EMALLOC); | p->sec = m->lastsec; | ||
return(NULL); | |||
} | |||
p->sec = mdoc->lastsec; | |||
p->line = line; | p->line = line; | ||
p->pos = pos; | p->pos = pos; | ||
p->tok = tok; | p->tok = tok; | ||
if (MDOC_TEXT != (p->type = type)) | p->type = type; | ||
assert(p->tok >= 0); | |||
/* Flag analysis. */ | |||
if (MDOC_SYNOPSIS & m->flags) | |||
p->flags |= MDOC_SYNPRETTY; | |||
else | |||
p->flags &= ~MDOC_SYNPRETTY; | |||
if (MDOC_NEWLINE & m->flags) | |||
p->flags |= MDOC_LINE; | |||
m->flags &= ~MDOC_NEWLINE; | |||
return(p); | return(p); | ||
} | } | ||
int | int | ||
mdoc_tail_alloc(struct mdoc *mdoc, int line, int pos, int tok) | mdoc_tail_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(mdoc, line, pos, tok, MDOC_TAIL); | p = node_alloc(m, line, pos, tok, MDOC_TAIL); | ||
if (NULL == p) | if ( ! node_append(m, p)) | ||
return(0); | return(0); | ||
return(node_append(mdoc, p)); | m->next = MDOC_NEXT_CHILD; | ||
return(1); | |||
} | } | ||
int | int | ||
mdoc_head_alloc(struct mdoc *mdoc, int line, int pos, int tok) | mdoc_head_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
assert(mdoc->first); | assert(m->first); | ||
assert(mdoc->last); | assert(m->last); | ||
p = node_alloc(mdoc, line, pos, tok, MDOC_HEAD); | p = node_alloc(m, line, pos, tok, MDOC_HEAD); | ||
if (NULL == p) | if ( ! node_append(m, p)) | ||
return(0); | return(0); | ||
return(node_append(mdoc, p)); | m->next = MDOC_NEXT_CHILD; | ||
return(1); | |||
} | } | ||
int | int | ||
mdoc_body_alloc(struct mdoc *mdoc, int line, int pos, int tok) | mdoc_body_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(mdoc, line, pos, tok, MDOC_BODY); | p = node_alloc(m, line, pos, tok, MDOC_BODY); | ||
if (NULL == p) | if ( ! node_append(m, p)) | ||
return(0); | return(0); | ||
return(node_append(mdoc, p)); | m->next = MDOC_NEXT_CHILD; | ||
return(1); | |||
} | } | ||
int | int | ||
mdoc_block_alloc(struct mdoc *mdoc, int line, int pos, | mdoc_endbody_alloc(struct mdoc *m, int line, int pos, enum mdoct tok, | ||
int tok, struct mdoc_arg *args) | struct mdoc_node *body, enum mdoc_endbody end) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(mdoc, line, pos, tok, MDOC_BLOCK); | p = node_alloc(m, line, pos, tok, MDOC_BODY); | ||
if (NULL == p) | p->pending = body; | ||
p->end = end; | |||
if ( ! node_append(m, p)) | |||
return(0); | return(0); | ||
m->next = MDOC_NEXT_SIBLING; | |||
return(1); | |||
} | |||
int | |||
mdoc_block_alloc(struct mdoc *m, int line, int pos, | |||
enum mdoct tok, struct mdoc_arg *args) | |||
{ | |||
struct mdoc_node *p; | |||
p = node_alloc(m, line, pos, tok, MDOC_BLOCK); | |||
p->args = args; | p->args = args; | ||
if (p->args) | if (p->args) | ||
(args->refcnt)++; | (args->refcnt)++; | ||
return(node_append(mdoc, p)); | |||
switch (tok) { | |||
case (MDOC_Bd): | |||
/* FALLTHROUGH */ | |||
case (MDOC_Bf): | |||
/* FALLTHROUGH */ | |||
case (MDOC_Bl): | |||
/* FALLTHROUGH */ | |||
case (MDOC_Rs): | |||
p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); | |||
break; | |||
default: | |||
break; | |||
} | |||
if ( ! node_append(m, p)) | |||
return(0); | |||
m->next = MDOC_NEXT_CHILD; | |||
return(1); | |||
} | } | ||
int | int | ||
mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos, | mdoc_elem_alloc(struct mdoc *m, int line, int pos, | ||
int tok, struct mdoc_arg *args) | enum mdoct tok, struct mdoc_arg *args) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(mdoc, line, pos, tok, MDOC_ELEM); | p = node_alloc(m, line, pos, tok, MDOC_ELEM); | ||
if (NULL == p) | |||
return(0); | |||
p->args = args; | p->args = args; | ||
if (p->args) | if (p->args) | ||
(args->refcnt)++; | (args->refcnt)++; | ||
return(node_append(mdoc, p)); | |||
switch (tok) { | |||
case (MDOC_An): | |||
p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); | |||
break; | |||
default: | |||
break; | |||
} | |||
if ( ! node_append(m, p)) | |||
return(0); | |||
m->next = MDOC_NEXT_CHILD; | |||
return(1); | |||
} | } | ||
static int | |||
mdoc_span_alloc(struct mdoc *m, const struct tbl_span *sp) | |||
{ | |||
struct mdoc_node *n; | |||
/* FIXME: grab from tbl_span. */ | |||
n = node_alloc(m, 0, 0, MDOC_MAX, MDOC_TBL); | |||
n->span = sp; | |||
if ( ! node_append(m, n)) | |||
return(0); | |||
m->next = MDOC_NEXT_SIBLING; | |||
return(1); | |||
} | |||
int | int | ||
mdoc_word_alloc(struct mdoc *mdoc, | mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p) | ||
int line, int pos, const char *word) | |||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *n; | ||
size_t sv, len; | |||
p = node_alloc(mdoc, line, pos, -1, MDOC_TEXT); | len = strlen(p); | ||
if (NULL == p) | |||
n = node_alloc(m, line, pos, MDOC_MAX, MDOC_TEXT); | |||
n->string = mandoc_malloc(len + 1); | |||
sv = strlcpy(n->string, p, len + 1); | |||
/* Prohibit truncation. */ | |||
assert(sv < len + 1); | |||
if ( ! node_append(m, n)) | |||
return(0); | return(0); | ||
if (NULL == (p->string = strdup(word))) { | |||
(void)verr(mdoc, EMALLOC); | m->next = MDOC_NEXT_SIBLING; | ||
return(0); | return(1); | ||
} | |||
return(node_append(mdoc, p)); | |||
} | } | ||
void | static void | ||
mdoc_node_free(struct mdoc_node *p) | mdoc_node_free(struct mdoc_node *p) | ||
{ | { | ||
if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type) | |||
free(p->norm); | |||
if (p->string) | if (p->string) | ||
free(p->string); | free(p->string); | ||
if (p->args) | if (p->args) | ||
|
|
||
} | } | ||
static void | |||
mdoc_node_unlink(struct mdoc *m, struct mdoc_node *n) | |||
{ | |||
/* Adjust siblings. */ | |||
if (n->prev) | |||
n->prev->next = n->next; | |||
if (n->next) | |||
n->next->prev = n->prev; | |||
/* Adjust parent. */ | |||
if (n->parent) { | |||
n->parent->nchild--; | |||
if (n->parent->child == n) | |||
n->parent->child = n->prev ? n->prev : n->next; | |||
if (n->parent->last == n) | |||
n->parent->last = n->prev ? n->prev : NULL; | |||
} | |||
/* Adjust parse point, if applicable. */ | |||
if (m && m->last == n) { | |||
if (n->prev) { | |||
m->last = n->prev; | |||
m->next = MDOC_NEXT_SIBLING; | |||
} else { | |||
m->last = n->parent; | |||
m->next = MDOC_NEXT_CHILD; | |||
} | |||
} | |||
if (m && m->first == n) | |||
m->first = NULL; | |||
} | |||
void | void | ||
mdoc_node_freelist(struct mdoc_node *p) | mdoc_node_delete(struct mdoc *m, struct mdoc_node *p) | ||
{ | { | ||
if (p->child) | while (p->child) { | ||
mdoc_node_freelist(p->child); | assert(p->nchild); | ||
if (p->next) | mdoc_node_delete(m, p->child); | ||
mdoc_node_freelist(p->next); | } | ||
assert(0 == p->nchild); | |||
mdoc_node_unlink(m, p); | |||
mdoc_node_free(p); | mdoc_node_free(p); | ||
} | } | ||
|
|
||
* control character. | * control character. | ||
*/ | */ | ||
static int | static int | ||
parsetext(struct mdoc *m, int line, char *buf) | mdoc_ptext(struct mdoc *m, int line, char *buf, int offs) | ||
{ | { | ||
char *c, *ws, *end; | |||
struct mdoc_node *n; | |||
if (SEC_PROLOGUE == m->lastnamed) | /* Ignore bogus comments. */ | ||
return(perr(m, line, 0, ETEXTPROL)); | |||
if (0 == buf[0] && ! (MDOC_LITERAL & m->flags)) | if ('\\' == buf[offs] && | ||
return(perr(m, line, 0, ENOBLANK)); | '.' == buf[offs + 1] && | ||
'"' == buf[offs + 2]) { | |||
mdoc_pmsg(m, line, offs, MANDOCERR_BADCOMMENT); | |||
return(1); | |||
} | |||
if ( ! mdoc_word_alloc(m, line, 0, buf)) | /* No text before an initial macro. */ | ||
if (SEC_NONE == m->lastnamed) { | |||
mdoc_pmsg(m, line, offs, MANDOCERR_NOTEXT); | |||
return(1); | |||
} | |||
assert(m->last); | |||
n = m->last; | |||
/* | |||
* Divert directly to list processing if we're encountering a | |||
* columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry | |||
* (a MDOC_BODY means it's already open, in which case we should | |||
* process within its context in the normal way). | |||
*/ | |||
if (MDOC_Bl == n->tok && MDOC_BODY == n->type && | |||
LIST_column == n->norm->Bl.type) { | |||
/* `Bl' is open without any children. */ | |||
m->flags |= MDOC_FREECOL; | |||
return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf)); | |||
} | |||
if (MDOC_It == n->tok && MDOC_BLOCK == n->type && | |||
NULL != n->parent && | |||
MDOC_Bl == n->parent->tok && | |||
LIST_column == n->parent->norm->Bl.type) { | |||
/* `Bl' has block-level `It' children. */ | |||
m->flags |= MDOC_FREECOL; | |||
return(mdoc_macro(m, MDOC_It, line, offs, &offs, buf)); | |||
} | |||
/* | |||
* 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 (mandoc_hyph(buf + offs, c)) | |||
*c = ASCII_HYPH; | |||
ws = NULL; | |||
break; | |||
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_LITERAL & m->flags) | |||
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) | |||
mdoc_pmsg(m, line, (int)(ws-buf), MANDOCERR_EOLNSPACE); | |||
if ('\0' == buf[offs] && ! (MDOC_LITERAL & m->flags)) { | |||
mdoc_pmsg(m, line, (int)(c-buf), MANDOCERR_NOBLANKLN); | |||
/* | |||
* 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. | |||
*/ | |||
if ( ! mdoc_elem_alloc(m, line, offs, MDOC_sp, NULL)) | |||
return(0); | |||
m->next = MDOC_NEXT_SIBLING; | |||
return(1); | |||
} | |||
if ( ! mdoc_word_alloc(m, line, offs, buf+offs)) | |||
return(0); | return(0); | ||
m->next = MDOC_NEXT_SIBLING; | if (MDOC_LITERAL & m->flags) | ||
return(1); | 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. | |||
*/ | |||
static int | assert(buf < end); | ||
macrowarn(struct mdoc *m, int ln, const char *buf) | |||
{ | |||
if ( ! (MDOC_IGN_MACRO & m->pflags)) | |||
return(mdoc_perr(m, ln, 1, | |||
"unknown macro: %s%s", | |||
buf, strlen(buf) > 3 ? "..." : "")); | |||
return(mdoc_pwarn(m, ln, 1, WARN_SYNTAX, | |||
"unknown macro: %s%s", | |||
buf, strlen(buf) > 3 ? "..." : "")); | |||
} | |||
if (mandoc_eos(buf+offs, (size_t)(end-buf-offs), 0)) | |||
m->last->flags |= MDOC_EOS; | |||
return(1); | |||
} | |||
/* | /* | ||
* Parse a macro line, that is, a line beginning with the control | * Parse a macro line, that is, a line beginning with the control | ||
* character. | * character. | ||
*/ | */ | ||
int | static int | ||
parsemacro(struct mdoc *m, int ln, char *buf) | mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs) | ||
{ | { | ||
int i, c; | enum mdoct tok; | ||
int i, j, sv; | |||
char mac[5]; | char mac[5]; | ||
struct mdoc_node *n; | |||
/* Comments and empties are quickly ignored. */ | /* Empty lines are ignored. */ | ||
if (0 == buf[1]) | offs++; | ||
if ('\0' == buf[offs]) | |||
return(1); | return(1); | ||
if (' ' == buf[1]) { | i = offs; | ||
i = 2; | |||
while (buf[i] && ' ' == buf[i]) | /* Accept tabs/whitespace after the initial control char. */ | ||
if (' ' == buf[i] || '\t' == buf[i]) { | |||
i++; | |||
while (buf[i] && (' ' == buf[i] || '\t' == buf[i])) | |||
i++; | i++; | ||
if (0 == buf[i]) | if ('\0' == buf[i]) | ||
return(1); | return(1); | ||
return(perr(m, ln, 1, ESPACE)); | |||
} | } | ||
if (buf[1] && '\\' == buf[1]) | sv = i; | ||
if (buf[2] && '\"' == buf[2]) | |||
return(1); | |||
/* Copy the first word into a nil-terminated buffer. */ | /* | ||
* Copy the first word into a nil-terminated buffer. | |||
* Stop copying when a tab, space, or eoln is encountered. | |||
*/ | |||
for (i = 1; i < 5; i++) { | j = 0; | ||
if (0 == (mac[i - 1] = buf[i])) | while (j < 4 && '\0' != buf[i] && ' ' != buf[i] && '\t' != buf[i]) | ||
break; | mac[j++] = buf[i++]; | ||
else if (' ' == buf[i]) | mac[j] = '\0'; | ||
break; | |||
tok = (j > 1 || j < 4) ? mdoc_hash_find(mac) : MDOC_MAX; | |||
if (MDOC_MAX == tok) { | |||
mdoc_vmsg(m, MANDOCERR_MACRO, ln, sv, "%s", buf + sv - 1); | |||
return(1); | |||
} | } | ||
mac[i - 1] = 0; | /* Disregard the first trailing tab, if applicable. */ | ||
if (i == 5 || i <= 2) { | if ('\t' == buf[i]) | ||
if ( ! macrowarn(m, ln, mac)) | i++; | ||
/* Jump to the next non-whitespace word. */ | |||
while (buf[i] && ' ' == buf[i]) | |||
i++; | |||
/* | |||
* 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[i] && ' ' == buf[i - 1]) | |||
mdoc_pmsg(m, ln, i - 1, MANDOCERR_EOLNSPACE); | |||
/* | |||
* If an initial macro or a list invocation, divert directly | |||
* into macro processing. | |||
*/ | |||
if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) { | |||
if ( ! mdoc_macro(m, tok, ln, sv, &i, buf)) | |||
goto err; | goto err; | ||
return(1); | return(1); | ||
} | } | ||
if (MDOC_MAX == (c = mdoc_hash_find(m->htab, mac))) { | n = m->last; | ||
if ( ! macrowarn(m, ln, mac)) | assert(m->last); | ||
/* | |||
* If the first macro of a `Bl -column', open an `It' block | |||
* context around the parsed macro. | |||
*/ | |||
if (MDOC_Bl == n->tok && MDOC_BODY == n->type && | |||
LIST_column == n->norm->Bl.type) { | |||
m->flags |= MDOC_FREECOL; | |||
if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) | |||
goto err; | goto err; | ||
return(1); | return(1); | ||
} | } | ||
/* The macro is sane. Jump to the next word. */ | /* | ||
* 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. | |||
*/ | |||
while (buf[i] && ' ' == buf[i]) | if (MDOC_It == n->tok && MDOC_BLOCK == n->type && | ||
i++; | NULL != n->parent && | ||
MDOC_Bl == n->parent->tok && | |||
LIST_column == n->parent->norm->Bl.type) { | |||
m->flags |= MDOC_FREECOL; | |||
if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) | |||
goto err; | |||
return(1); | |||
} | |||
/* Begin recursive parse sequence. */ | /* Normal processing of a macro. */ | ||
if ( ! mdoc_macro(m, c, ln, 1, &i, buf)) | if ( ! mdoc_macro(m, tok, ln, sv, &i, buf)) | ||
goto err; | goto err; | ||
return(1); | return(1); | ||
|
|
||
m->flags |= MDOC_HALT; | m->flags |= MDOC_HALT; | ||
return(0); | return(0); | ||
} | } | ||