Return to mdoc.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.131, 2010/05/14 17:31:25 | version 1.212, 2014/03/30 19:47:48 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se> | * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2010, 2012, 2013, 2014 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 | ||
|
|
||
#include <string.h> | #include <string.h> | ||
#include <time.h> | #include <time.h> | ||
#include "mdoc.h" | |||
#include "mandoc.h" | |||
#include "mandoc_aux.h" | |||
#include "libmdoc.h" | #include "libmdoc.h" | ||
#include "libmandoc.h" | #include "libmandoc.h" | ||
const char *const __mdoc_merrnames[MERRMAX] = { | |||
"trailing whitespace", /* ETAILWS */ | |||
"unexpected quoted parameter", /* EQUOTPARM */ | |||
"unterminated quoted parameter", /* EQUOTTERM */ | |||
"argument parameter suggested", /* EARGVAL */ | |||
"macro disallowed in prologue", /* EBODYPROL */ | |||
"macro disallowed in body", /* EPROLBODY */ | |||
"text disallowed in prologue", /* ETEXTPROL */ | |||
"blank line disallowed", /* ENOBLANK */ | |||
"text parameter too long", /* ETOOLONG */ | |||
"invalid escape sequence", /* EESCAPE */ | |||
"invalid character", /* EPRINT */ | |||
"document has no body", /* ENODAT */ | |||
"document has no prologue", /* ENOPROLOGUE */ | |||
"expected line arguments", /* ELINE */ | |||
"invalid AT&T argument", /* EATT */ | |||
"default name not yet set", /* ENAME */ | |||
"missing list type", /* ELISTTYPE */ | |||
"missing display type", /* EDISPTYPE */ | |||
"too many display types", /* EMULTIDISP */ | |||
"too many list types", /* EMULTILIST */ | |||
"NAME section must be first", /* ESECNAME */ | |||
"badly-formed NAME section", /* ENAMESECINC */ | |||
"argument repeated", /* EARGREP */ | |||
"expected boolean parameter", /* EBOOL */ | |||
"inconsistent column syntax", /* ECOLMIS */ | |||
"nested display invalid", /* ENESTDISP */ | |||
"width argument missing", /* EMISSWIDTH */ | |||
"invalid section for this manual section", /* EWRONGMSEC */ | |||
"section out of conventional order", /* ESECOOO */ | |||
"section repeated", /* ESECREP */ | |||
"invalid standard argument", /* EBADSTAND */ | |||
"multi-line arguments discouraged", /* ENOMULTILINE */ | |||
"multi-line arguments suggested", /* EMULTILINE */ | |||
"line arguments discouraged", /* ENOLINE */ | |||
"prologue macro out of conventional order", /* EPROLOOO */ | |||
"prologue macro repeated", /* EPROLREP */ | |||
"invalid section", /* EBADSEC */ | |||
"invalid font mode", /* EFONT */ | |||
"invalid date syntax", /* EBADDATE */ | |||
"invalid number format", /* ENUMFMT */ | |||
"superfluous width argument", /* ENOWIDTH */ | |||
"system: utsname error", /* EUTSNAME */ | |||
"obsolete macro", /* EOBS */ | |||
"end-of-line scope violation", /* EIMPBRK */ | |||
"empty macro ignored", /* EIGNE */ | |||
"unclosed explicit scope", /* EOPEN */ | |||
"unterminated quoted phrase", /* EQUOTPHR */ | |||
"closure macro without prior context", /* ENOCTX */ | |||
"no description found for library", /* ELIB */ | |||
"bad child for parent context", /* EBADCHILD */ | |||
"list arguments preceding type", /* ENOTYPE */ | |||
"deprecated comment style", /* EBADCOMMENT */ | |||
}; | |||
const char *const __mdoc_macronames[MDOC_MAX] = { | const char *const __mdoc_macronames[MDOC_MAX] = { | ||
"Ap", "Dd", "Dt", "Os", | "Ap", "Dd", "Dt", "Os", | ||
"Sh", "Ss", "Pp", "D1", | "Sh", "Ss", "Pp", "D1", | ||
|
|
||
/* LINTED */ | /* LINTED */ | ||
"Dx", "%Q", "br", "sp", | "Dx", "%Q", "br", "sp", | ||
/* LINTED */ | /* LINTED */ | ||
"%U" | "%U", "Ta", "ll", | ||
}; | }; | ||
const char *const __mdoc_argnames[MDOC_ARG_MAX] = { | const char *const __mdoc_argnames[MDOC_ARG_MAX] = { | ||
|
|
||
enum mdoct, 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 mdoc_ptext(struct mdoc *, int, char *); | #if 0 | ||
static int mdoc_pmacro(struct mdoc *, int, char *); | static int mdoc_preptext(struct mdoc *, int, char *, int); | ||
static int macrowarn(struct mdoc *, int, const char *); | #endif | ||
static int mdoc_ptext(struct mdoc *, int, char *, int); | |||
static int mdoc_pmacro(struct mdoc *, int, char *, int); | |||
const struct mdoc_node * | const struct mdoc_node * | ||
mdoc_node(const struct mdoc *m) | mdoc_node(const struct mdoc *mdoc) | ||
{ | { | ||
return(MDOC_HALT & m->flags ? NULL : m->first); | assert( ! (MDOC_HALT & mdoc->flags)); | ||
return(mdoc->first); | |||
} | } | ||
const struct mdoc_meta * | const struct mdoc_meta * | ||
mdoc_meta(const struct mdoc *m) | mdoc_meta(const struct mdoc *mdoc) | ||
{ | { | ||
return(MDOC_HALT & m->flags ? NULL : &m->meta); | assert( ! (MDOC_HALT & mdoc->flags)); | ||
return(&mdoc->meta); | |||
} | } | ||
|
|
||
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); | |||
if (mdoc->meta.date) | |||
free(mdoc->meta.date); | |||
} | } | ||
|
|
||
mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node)); | mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node)); | ||
mdoc->first = mdoc->last; | mdoc->first = mdoc->last; | ||
mdoc->last->type = MDOC_ROOT; | mdoc->last->type = MDOC_ROOT; | ||
mdoc->last->tok = MDOC_MAX; | |||
mdoc->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
} | } | ||
|
|
||
* Allocate volatile and non-volatile parse resources. | * 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 roff *roff, struct mparse *parse, | ||
char *defos, int quick) | |||
{ | { | ||
struct mdoc *p; | struct mdoc *p; | ||
p = mandoc_calloc(1, sizeof(struct mdoc)); | p = mandoc_calloc(1, sizeof(struct mdoc)); | ||
if (cb) | p->parse = parse; | ||
memcpy(&p->cb, cb, sizeof(struct mdoc_cb)); | p->defos = defos; | ||
p->quick = quick; | |||
p->roff = roff; | |||
p->data = data; | |||
p->pflags = pflags; | |||
mdoc_hash_init(); | mdoc_hash_init(); | ||
mdoc_alloc1(p); | mdoc_alloc1(p); | ||
return(p); | return(p); | ||
|
|
||
* 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 *mdoc) | ||
{ | { | ||
if (MDOC_HALT & m->flags) | assert( ! (MDOC_HALT & mdoc->flags)); | ||
return(0); | if (mdoc_macroend(mdoc)) | ||
else if (mdoc_macroend(m)) | |||
return(1); | return(1); | ||
m->flags |= MDOC_HALT; | mdoc->flags |= MDOC_HALT; | ||
return(0); | return(0); | ||
} | } | ||
/* | |||
* Main parse routine. Parses a single line -- really just hands off to | |||
* the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). | |||
*/ | |||
int | int | ||
mdoc_parseln(struct mdoc *m, int ln, char *buf) | mdoc_addeqn(struct mdoc *mdoc, const struct eqn *ep) | ||
{ | { | ||
struct mdoc_node *n; | |||
if (MDOC_HALT & m->flags) | assert( ! (MDOC_HALT & mdoc->flags)); | ||
return(0); | |||
m->flags |= MDOC_NEWLINE; | /* No text before an initial macro. */ | ||
return('.' == *buf ? | |||
mdoc_pmacro(m, ln, buf) : | |||
mdoc_ptext(m, ln, buf)); | |||
} | |||
if (SEC_NONE == mdoc->lastnamed) { | |||
mdoc_pmsg(mdoc, ep->ln, ep->pos, MANDOCERR_NOTEXT); | |||
return(1); | |||
} | |||
int | n = node_alloc(mdoc, ep->ln, ep->pos, MDOC_MAX, MDOC_EQN); | ||
mdoc_verr(struct mdoc *mdoc, int ln, int pos, | n->eqn = ep; | ||
const char *fmt, ...) | |||
{ | |||
char buf[256]; | |||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_err) | if ( ! node_append(mdoc, n)) | ||
return(0); | return(0); | ||
va_start(ap, fmt); | mdoc->next = MDOC_NEXT_SIBLING; | ||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | return(1); | ||
va_end(ap); | |||
return((*mdoc->cb.mdoc_err)(mdoc->data, ln, pos, buf)); | |||
} | } | ||
int | int | ||
mdoc_vwarn(struct mdoc *mdoc, int ln, int pos, const char *fmt, ...) | mdoc_addspan(struct mdoc *mdoc, const struct tbl_span *sp) | ||
{ | { | ||
char buf[256]; | struct mdoc_node *n; | ||
va_list ap; | |||
if (NULL == mdoc->cb.mdoc_warn) | assert( ! (MDOC_HALT & mdoc->flags)); | ||
return(0); | |||
va_start(ap, fmt); | /* No text before an initial macro. */ | ||
(void)vsnprintf(buf, sizeof(buf) - 1, fmt, ap); | |||
va_end(ap); | |||
return((*mdoc->cb.mdoc_warn)(mdoc->data, ln, pos, buf)); | if (SEC_NONE == mdoc->lastnamed) { | ||
mdoc_pmsg(mdoc, sp->line, 0, MANDOCERR_NOTEXT); | |||
return(1); | |||
} | |||
n = node_alloc(mdoc, sp->line, 0, MDOC_MAX, MDOC_TBL); | |||
n->span = sp; | |||
if ( ! node_append(mdoc, n)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_SIBLING; | |||
return(1); | |||
} | } | ||
/* | |||
* Main parse routine. Parses a single line -- really just hands off to | |||
* the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). | |||
*/ | |||
int | int | ||
mdoc_err(struct mdoc *m, int line, int pos, int iserr, enum merr type) | mdoc_parseln(struct mdoc *mdoc, int ln, char *buf, int offs) | ||
{ | { | ||
const char *p; | |||
p = __mdoc_merrnames[(int)type]; | assert( ! (MDOC_HALT & mdoc->flags)); | ||
assert(p); | |||
if (iserr) | mdoc->flags |= MDOC_NEWLINE; | ||
return(mdoc_verr(m, line, pos, p)); | |||
return(mdoc_vwarn(m, line, pos, p)); | /* | ||
* Let the roff nS register switch SYNOPSIS mode early, | |||
* such that the parser knows at all times | |||
* whether this mode is on or off. | |||
* Note that this mode is also switched by the Sh macro. | |||
*/ | |||
if (roff_getreg(mdoc->roff, "nS")) | |||
mdoc->flags |= MDOC_SYNOPSIS; | |||
else | |||
mdoc->flags &= ~MDOC_SYNOPSIS; | |||
return(roff_getcontrol(mdoc->roff, buf, &offs) ? | |||
mdoc_pmacro(mdoc, ln, buf, offs) : | |||
mdoc_ptext(mdoc, ln, buf, offs)); | |||
} | } | ||
int | int | ||
mdoc_macro(struct mdoc *m, enum mdoct tok, | mdoc_macro(MACRO_PROT_ARGS) | ||
int ln, int pp, int *pos, char *buf) | |||
{ | { | ||
assert(tok < MDOC_MAX); | assert(tok < MDOC_MAX); | ||
/* If we're in the body, deny prologue calls. */ | /* If we're in the body, deny prologue calls. */ | ||
if (MDOC_PROLOGUE & mdoc_macros[tok].flags && | if (MDOC_PROLOGUE & mdoc_macros[tok].flags && | ||
MDOC_PBODY & m->flags) | MDOC_PBODY & mdoc->flags) { | ||
return(mdoc_perr(m, ln, pp, EPROLBODY)); | mdoc_pmsg(mdoc, line, ppos, MANDOCERR_BADBODY); | ||
return(1); | |||
} | |||
/* If we're in the prologue, deny "body" macros. */ | /* If we're in the prologue, deny "body" macros. */ | ||
if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && | if ( ! (MDOC_PROLOGUE & mdoc_macros[tok].flags) && | ||
! (MDOC_PBODY & m->flags)) { | ! (MDOC_PBODY & mdoc->flags)) { | ||
if ( ! mdoc_pwarn(m, ln, pp, EBODYPROL)) | mdoc_pmsg(mdoc, line, ppos, MANDOCERR_BADPROLOG); | ||
return(0); | if (NULL == mdoc->meta.msec) | ||
if (NULL == m->meta.title) | mdoc->meta.msec = mandoc_strdup("1"); | ||
m->meta.title = mandoc_strdup("unknown"); | if (NULL == mdoc->meta.title) | ||
if (NULL == m->meta.vol) | mdoc->meta.title = mandoc_strdup("UNKNOWN"); | ||
m->meta.vol = mandoc_strdup("local"); | if (NULL == mdoc->meta.vol) | ||
if (NULL == m->meta.os) | mdoc->meta.vol = mandoc_strdup("LOCAL"); | ||
m->meta.os = mandoc_strdup("local"); | if (NULL == mdoc->meta.os) | ||
if (0 == m->meta.date) | mdoc->meta.os = mandoc_strdup("LOCAL"); | ||
m->meta.date = time(NULL); | if (NULL == mdoc->meta.date) | ||
m->flags |= MDOC_PBODY; | mdoc->meta.date = mandoc_normdate | ||
(mdoc->parse, NULL, line, ppos); | |||
mdoc->flags |= MDOC_PBODY; | |||
} | } | ||
return((*mdoc_macros[tok].fp)(m, tok, ln, pp, pos, buf)); | return((*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf)); | ||
} | } | ||
|
|
||
p->parent->nchild++; | 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): | |||
if (ENDBODY_NOT != p->end) | |||
break; | |||
/* 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 *m, int line, int pos, | node_alloc(struct mdoc *mdoc, int line, int pos, | ||
enum mdoct tok, enum mdoc_type type) | enum mdoct tok, enum mdoc_type type) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = mandoc_calloc(1, sizeof(struct mdoc_node)); | p = mandoc_calloc(1, sizeof(struct mdoc_node)); | ||
p->sec = m->lastsec; | p->sec = mdoc->lastsec; | ||
p->line = line; | p->line = line; | ||
p->pos = pos; | p->pos = pos; | ||
p->lastline = line; | |||
p->tok = tok; | p->tok = tok; | ||
p->type = type; | p->type = type; | ||
if (MDOC_NEWLINE & m->flags) | |||
/* Flag analysis. */ | |||
if (MDOC_SYNOPSIS & mdoc->flags) | |||
p->flags |= MDOC_SYNPRETTY; | |||
else | |||
p->flags &= ~MDOC_SYNPRETTY; | |||
if (MDOC_NEWLINE & mdoc->flags) | |||
p->flags |= MDOC_LINE; | p->flags |= MDOC_LINE; | ||
m->flags &= ~MDOC_NEWLINE; | mdoc->flags &= ~MDOC_NEWLINE; | ||
return(p); | return(p); | ||
} | } | ||
int | int | ||
mdoc_tail_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) | mdoc_tail_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(m, line, pos, tok, MDOC_TAIL); | p = node_alloc(mdoc, line, pos, tok, MDOC_TAIL); | ||
if ( ! node_append(m, p)) | if ( ! node_append(mdoc, p)) | ||
return(0); | return(0); | ||
m->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
return(1); | return(1); | ||
} | } | ||
int | int | ||
mdoc_head_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) | mdoc_head_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
assert(m->first); | assert(mdoc->first); | ||
assert(m->last); | assert(mdoc->last); | ||
p = node_alloc(m, line, pos, tok, MDOC_HEAD); | p = node_alloc(mdoc, line, pos, tok, MDOC_HEAD); | ||
if ( ! node_append(m, p)) | if ( ! node_append(mdoc, p)) | ||
return(0); | return(0); | ||
m->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
return(1); | return(1); | ||
} | } | ||
int | int | ||
mdoc_body_alloc(struct mdoc *m, int line, int pos, enum mdoct tok) | mdoc_body_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(m, line, pos, tok, MDOC_BODY); | p = node_alloc(mdoc, line, pos, tok, MDOC_BODY); | ||
if ( ! node_append(m, p)) | if ( ! node_append(mdoc, p)) | ||
return(0); | return(0); | ||
m->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
return(1); | return(1); | ||
} | } | ||
int | int | ||
mdoc_block_alloc(struct mdoc *m, int line, int pos, | mdoc_endbody_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok, | ||
struct mdoc_node *body, enum mdoc_endbody end) | |||
{ | |||
struct mdoc_node *p; | |||
p = node_alloc(mdoc, line, pos, tok, MDOC_BODY); | |||
p->pending = body; | |||
p->norm = body->norm; | |||
p->end = end; | |||
if ( ! node_append(mdoc, p)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_SIBLING; | |||
return(1); | |||
} | |||
int | |||
mdoc_block_alloc(struct mdoc *mdoc, int line, int pos, | |||
enum mdoct tok, struct mdoc_arg *args) | enum mdoct tok, struct mdoc_arg *args) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(m, line, pos, tok, MDOC_BLOCK); | p = node_alloc(mdoc, line, pos, tok, MDOC_BLOCK); | ||
p->args = args; | p->args = args; | ||
if (p->args) | if (p->args) | ||
(args->refcnt)++; | (args->refcnt)++; | ||
if ( ! node_append(m, 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(mdoc, p)) | |||
return(0); | return(0); | ||
m->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
return(1); | return(1); | ||
} | } | ||
int | int | ||
mdoc_elem_alloc(struct mdoc *m, int line, int pos, | mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos, | ||
enum mdoct tok, struct mdoc_arg *args) | enum mdoct tok, struct mdoc_arg *args) | ||
{ | { | ||
struct mdoc_node *p; | struct mdoc_node *p; | ||
p = node_alloc(m, line, pos, tok, MDOC_ELEM); | p = node_alloc(mdoc, line, pos, tok, MDOC_ELEM); | ||
p->args = args; | p->args = args; | ||
if (p->args) | if (p->args) | ||
(args->refcnt)++; | (args->refcnt)++; | ||
if ( ! node_append(m, p)) | |||
switch (tok) { | |||
case (MDOC_An): | |||
p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); | |||
break; | |||
default: | |||
break; | |||
} | |||
if ( ! node_append(mdoc, p)) | |||
return(0); | return(0); | ||
m->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
return(1); | return(1); | ||
} | } | ||
int | int | ||
mdoc_word_alloc(struct mdoc *m, int line, int pos, const char *p) | mdoc_word_alloc(struct mdoc *mdoc, int line, int pos, const char *p) | ||
{ | { | ||
struct mdoc_node *n; | struct mdoc_node *n; | ||
size_t sv, len; | |||
len = strlen(p); | n = node_alloc(mdoc, line, pos, MDOC_MAX, MDOC_TEXT); | ||
n->string = roff_strdup(mdoc->roff, p); | |||
n = node_alloc(m, line, pos, MDOC_MAX, MDOC_TEXT); | if ( ! node_append(mdoc, n)) | ||
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); | ||
m->next = MDOC_NEXT_SIBLING; | mdoc->next = MDOC_NEXT_SIBLING; | ||
return(1); | return(1); | ||
} | } | ||
void | void | ||
mdoc_word_append(struct mdoc *mdoc, const char *p) | |||
{ | |||
struct mdoc_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 = MDOC_NEXT_SIBLING; | |||
} | |||
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 | static void | ||
mdoc_node_unlink(struct mdoc *m, struct mdoc_node *n) | mdoc_node_unlink(struct mdoc *mdoc, struct mdoc_node *n) | ||
{ | { | ||
/* Adjust siblings. */ | /* Adjust siblings. */ | ||
|
|
||
n->parent->nchild--; | n->parent->nchild--; | ||
if (n->parent->child == n) | if (n->parent->child == n) | ||
n->parent->child = n->prev ? n->prev : n->next; | 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. */ | /* Adjust parse point, if applicable. */ | ||
if (m && m->last == n) { | if (mdoc && mdoc->last == n) { | ||
if (n->prev) { | if (n->prev) { | ||
m->last = n->prev; | mdoc->last = n->prev; | ||
m->next = MDOC_NEXT_SIBLING; | mdoc->next = MDOC_NEXT_SIBLING; | ||
} else { | } else { | ||
m->last = n->parent; | mdoc->last = n->parent; | ||
m->next = MDOC_NEXT_CHILD; | mdoc->next = MDOC_NEXT_CHILD; | ||
} | } | ||
} | } | ||
if (m && m->first == n) | if (mdoc && mdoc->first == n) | ||
m->first = NULL; | mdoc->first = NULL; | ||
} | } | ||
void | void | ||
mdoc_node_delete(struct mdoc *m, struct mdoc_node *p) | mdoc_node_delete(struct mdoc *mdoc, struct mdoc_node *p) | ||
{ | { | ||
while (p->child) { | while (p->child) { | ||
assert(p->nchild); | assert(p->nchild); | ||
mdoc_node_delete(m, p->child); | mdoc_node_delete(mdoc, p->child); | ||
} | } | ||
assert(0 == p->nchild); | assert(0 == p->nchild); | ||
mdoc_node_unlink(m, p); | mdoc_node_unlink(mdoc, p); | ||
mdoc_node_free(p); | mdoc_node_free(p); | ||
} | } | ||
int | |||
mdoc_node_relink(struct mdoc *mdoc, struct mdoc_node *p) | |||
{ | |||
mdoc_node_unlink(mdoc, p); | |||
return(node_append(mdoc, p)); | |||
} | |||
#if 0 | |||
/* | /* | ||
* Parse free-form text, that is, a line that does not begin with the | * Pre-treat a text line. | ||
* control character. | * Text lines can consist of equations, which must be handled apart from | ||
* the regular text. | |||
* Thus, use this function to step through a line checking if it has any | |||
* equations embedded in it. | |||
* This must handle multiple equations AND equations that do not end at | |||
* the end-of-line, i.e., will re-enter in the next roff parse. | |||
*/ | */ | ||
static int | static int | ||
mdoc_ptext(struct mdoc *m, int line, char *buf) | mdoc_preptext(struct mdoc *mdoc, int line, char *buf, int offs) | ||
{ | { | ||
int i; | char *start, *end; | ||
char delim; | |||
/* Ignore bogus comments. */ | while ('\0' != buf[offs]) { | ||
/* Mark starting position if eqn is set. */ | |||
start = NULL; | |||
if ('\0' != (delim = roff_eqndelim(mdoc->roff))) | |||
if (NULL != (start = strchr(buf + offs, delim))) | |||
*start++ = '\0'; | |||
if ('\\' == buf[0] && '.' == buf[1] && '\"' == buf[2]) | /* Parse text as normal. */ | ||
return(mdoc_pwarn(m, line, 0, EBADCOMMENT)); | if ( ! mdoc_ptext(mdoc, line, buf, offs)) | ||
return(0); | |||
/* No text before an initial macro. */ | /* Continue only if an equation exists. */ | ||
if (NULL == start) | |||
break; | |||
if (SEC_NONE == m->lastnamed) | /* Read past the end of the equation. */ | ||
return(mdoc_perr(m, line, 0, ETEXTPROL)); | offs += start - (buf + offs); | ||
assert(start == &buf[offs]); | |||
if (NULL != (end = strchr(buf + offs, delim))) { | |||
*end++ = '\0'; | |||
while (' ' == *end) | |||
end++; | |||
} | |||
/* Literal just gets pulled in as-is. */ | /* Parse the equation itself. */ | ||
roff_openeqn(mdoc->roff, NULL, line, offs, buf); | |||
if (MDOC_LITERAL & m->flags) | |||
return(mdoc_word_alloc(m, line, 0, buf)); | |||
/* Check for a blank line, which may also consist of spaces. */ | /* Process a finished equation? */ | ||
if (roff_closeeqn(mdoc->roff)) | |||
if ( ! mdoc_addeqn(mdoc, roff_eqn(mdoc->roff))) | |||
return(0); | |||
offs += (end - (buf + offs)); | |||
} | |||
for (i = 0; ' ' == buf[i]; i++) | return(1); | ||
/* Skip to first non-space. */ ; | } | ||
#endif | |||
if ('\0' == buf[i]) { | /* | ||
if ( ! mdoc_pwarn(m, line, 0, ENOBLANK)) | * Parse free-form text, that is, a line that does not begin with the | ||
return(0); | * control character. | ||
*/ | |||
static int | |||
mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs) | |||
{ | |||
char *c, *ws, *end; | |||
struct mdoc_node *n; | |||
/* | /* No text before an initial macro. */ | ||
* Insert a `Pp' 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, 0, MDOC_Pp, NULL)) | |||
return(0); | |||
m->next = MDOC_NEXT_SIBLING; | if (SEC_NONE == mdoc->lastnamed) { | ||
mdoc_pmsg(mdoc, line, offs, MANDOCERR_NOTEXT); | |||
return(1); | return(1); | ||
} | } | ||
/* | assert(mdoc->last); | ||
* Warn if the last un-escaped character is whitespace. Then | n = mdoc->last; | ||
* strip away the remaining spaces (tabs stay!). | |||
/* | |||
* 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). | |||
*/ | */ | ||
i = (int)strlen(buf); | if (MDOC_Bl == n->tok && MDOC_BODY == n->type && | ||
assert(i); | LIST_column == n->norm->Bl.type) { | ||
/* `Bl' is open without any children. */ | |||
mdoc->flags |= MDOC_FREECOL; | |||
return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf)); | |||
} | |||
if (' ' == buf[i - 1] || '\t' == buf[i - 1]) { | if (MDOC_It == n->tok && MDOC_BLOCK == n->type && | ||
if (i > 1 && '\\' != buf[i - 2]) | NULL != n->parent && | ||
if ( ! mdoc_pwarn(m, line, i - 1, ETAILWS)) | MDOC_Bl == n->parent->tok && | ||
return(0); | LIST_column == n->parent->norm->Bl.type) { | ||
/* `Bl' has block-level `It' children. */ | |||
mdoc->flags |= MDOC_FREECOL; | |||
return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf)); | |||
} | |||
for (--i; i && ' ' == buf[i]; i--) | /* | ||
/* Spin back to non-space. */ ; | * Search for the beginning of unescaped trailing whitespace (ws) | ||
* and for the first character not to be output (end). | |||
*/ | |||
/* Jump ahead of escaped whitespace. */ | /* FIXME: replace with strcspn(). */ | ||
i += '\\' == buf[i] ? 2 : 1; | ws = NULL; | ||
for (c = end = buf + offs; *c; c++) { | |||
buf[i] = '\0'; | switch (*c) { | ||
case ' ': | |||
if (NULL == ws) | |||
ws = c; | |||
continue; | |||
case '\t': | |||
/* | |||
* Always warn about trailing tabs, | |||
* even outside literal context, | |||
* where they should be put on the next line. | |||
*/ | |||
if (NULL == ws) | |||
ws = c; | |||
/* | |||
* Strip trailing tabs in literal context only; | |||
* outside, they affect the next line. | |||
*/ | |||
if (MDOC_LITERAL & mdoc->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'; | |||
/* Allocate the whole word. */ | if (ws) | ||
mdoc_pmsg(mdoc, line, (int)(ws-buf), MANDOCERR_EOLNSPACE); | |||
if ( ! mdoc_word_alloc(m, line, 0, buf)) | if ('\0' == buf[offs] && ! (MDOC_LITERAL & mdoc->flags)) { | ||
mdoc_pmsg(mdoc, 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(mdoc, line, offs, MDOC_sp, NULL)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_SIBLING; | |||
return(mdoc_valid_post(mdoc)); | |||
} | |||
if ( ! mdoc_word_alloc(mdoc, line, offs, buf+offs)) | |||
return(0); | return(0); | ||
if (MDOC_LITERAL & mdoc->flags) | |||
return(1); | |||
/* | /* | ||
* End-of-sentence check. If the last character is an unescaped | * End-of-sentence check. If the last character is an unescaped | ||
* EOS character, then flag the node as being the end of a | * EOS character, then flag the node as being the end of a | ||
* sentence. The front-end will know how to interpret this. | * sentence. The front-end will know how to interpret this. | ||
*/ | */ | ||
assert(i); | assert(buf < end); | ||
if (mandoc_eos(buf, (size_t)i)) | if (mandoc_eos(buf+offs, (size_t)(end-buf-offs))) | ||
m->last->flags |= MDOC_EOS; | mdoc->last->flags |= MDOC_EOS; | ||
return(1); | return(1); | ||
} | } | ||
static int | |||
macrowarn(struct mdoc *m, int ln, const char *buf) | |||
{ | |||
if ( ! (MDOC_IGN_MACRO & m->pflags)) | |||
return(mdoc_verr(m, ln, 0, "unknown macro: %s%s", | |||
buf, strlen(buf) > 3 ? "..." : "")); | |||
return(mdoc_vwarn(m, ln, 0, "unknown macro: %s%s", | |||
buf, strlen(buf) > 3 ? "..." : "")); | |||
} | |||
/* | /* | ||
* 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 | ||
mdoc_pmacro(struct mdoc *m, int ln, char *buf) | mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs) | ||
{ | { | ||
enum mdoct tok; | enum mdoct tok; | ||
int i, j, sv; | int i, sv; | ||
char mac[5]; | char mac[5]; | ||
struct mdoc_node *n; | |||
/* Empty lines are ignored. */ | /* Empty post-control lines are ignored. */ | ||
if ('\0' == buf[1]) | if ('"' == buf[offs]) { | ||
mdoc_pmsg(mdoc, ln, offs, MANDOCERR_BADCOMMENT); | |||
return(1); | return(1); | ||
} else if ('\0' == buf[offs]) | |||
return(1); | |||
i = 1; | sv = offs; | ||
/* Accept whitespace after the initial control char. */ | /* | ||
* Copy the first word into a nil-terminated buffer. | |||
* Stop copying when a tab, space, or eoln is encountered. | |||
*/ | |||
if (' ' == buf[i]) { | i = 0; | ||
i++; | while (i < 4 && '\0' != buf[offs] && | ||
while (buf[i] && ' ' == buf[i]) | ' ' != buf[offs] && '\t' != buf[offs]) | ||
i++; | mac[i++] = buf[offs++]; | ||
if ('\0' == buf[i]) | |||
return(1); | mac[i] = '\0'; | ||
tok = (i > 1 || i < 4) ? mdoc_hash_find(mac) : MDOC_MAX; | |||
if (MDOC_MAX == tok) { | |||
mandoc_vmsg(MANDOCERR_MACRO, mdoc->parse, | |||
ln, sv, "%s", buf + sv - 1); | |||
return(1); | |||
} | } | ||
sv = i; | /* Disregard the first trailing tab, if applicable. */ | ||
/* Copy the first word into a nil-terminated buffer. */ | if ('\t' == buf[offs]) | ||
offs++; | |||
for (j = 0; j < 4; j++, i++) { | /* Jump to the next non-whitespace word. */ | ||
if ('\0' == (mac[j] = buf[i])) | |||
break; | |||
else if (' ' == buf[i]) | |||
break; | |||
/* Check for invalid characters. */ | while (buf[offs] && ' ' == buf[offs]) | ||
offs++; | |||
if (isgraph((u_char)buf[i])) | /* | ||
continue; | * Trailing whitespace. Note that tabs are allowed to be passed | ||
return(mdoc_perr(m, ln, i, EPRINT)); | * into the parser as "text", so we only warn about spaces here. | ||
} | */ | ||
mac[j] = 0; | if ('\0' == buf[offs] && ' ' == buf[offs - 1]) | ||
mdoc_pmsg(mdoc, ln, offs - 1, MANDOCERR_EOLNSPACE); | |||
if (j == 4 || j < 2) { | /* | ||
if ( ! macrowarn(m, ln, mac)) | * If an initial macro or a list invocation, divert directly | ||
* into macro processing. | |||
*/ | |||
if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok) { | |||
if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf)) | |||
goto err; | goto err; | ||
return(1); | return(1); | ||
} | |||
if (MDOC_MAX == (tok = mdoc_hash_find(mac))) { | |||
if ( ! macrowarn(m, ln, mac)) | |||
goto err; | |||
return(1); | |||
} | } | ||
/* The macro is sane. Jump to the next word. */ | n = mdoc->last; | ||
assert(mdoc->last); | |||
while (buf[i] && ' ' == buf[i]) | /* | ||
i++; | * If the first macro of a `Bl -column', open an `It' block | ||
* context around the parsed macro. | |||
/* | |||
* 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]) | if (MDOC_Bl == n->tok && MDOC_BODY == n->type && | ||
if ( ! mdoc_pwarn(m, ln, i - 1, ETAILWS)) | LIST_column == n->norm->Bl.type) { | ||
mdoc->flags |= MDOC_FREECOL; | |||
if ( ! mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf)) | |||
goto err; | goto err; | ||
return(1); | |||
} | |||
/* | /* | ||
* Begin recursive parse sequence. Since we're at the start of | * If we're following a block-level `It' within a `Bl -column' | ||
* the line, we don't need to do callable/parseable checks. | * context (perhaps opened in the above block or in ptext()), | ||
* then open an `It' block context around the parsed macro. | |||
*/ | */ | ||
if ( ! mdoc_macro(m, tok, ln, sv, &i, 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) { | |||
mdoc->flags |= MDOC_FREECOL; | |||
if ( ! mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf)) | |||
goto err; | |||
return(1); | |||
} | |||
/* Normal processing of a macro. */ | |||
if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf)) | |||
goto err; | goto err; | ||
/* In quick mode (for mandocdb), abort after the NAME section. */ | |||
if (mdoc->quick && MDOC_Sh == tok && | |||
SEC_NAME != mdoc->last->sec) | |||
return(2); | |||
return(1); | return(1); | ||
err: /* Error out. */ | err: /* Error out. */ | ||
m->flags |= MDOC_HALT; | mdoc->flags |= MDOC_HALT; | ||
return(0); | return(0); | ||
} | } | ||
enum mdelim | |||
mdoc_isdelim(const char *p) | |||
{ | |||
if ('\0' == p[0]) | |||
return(DELIM_NONE); | |||
if ('\0' == p[1]) | |||
switch (p[0]) { | |||
case('('): | |||
/* FALLTHROUGH */ | |||
case('['): | |||
return(DELIM_OPEN); | |||
case('|'): | |||
return(DELIM_MIDDLE); | |||
case('.'): | |||
/* FALLTHROUGH */ | |||
case(','): | |||
/* FALLTHROUGH */ | |||
case(';'): | |||
/* FALLTHROUGH */ | |||
case(':'): | |||
/* FALLTHROUGH */ | |||
case('?'): | |||
/* FALLTHROUGH */ | |||
case('!'): | |||
/* FALLTHROUGH */ | |||
case(')'): | |||
/* FALLTHROUGH */ | |||
case(']'): | |||
return(DELIM_CLOSE); | |||
default: | |||
return(DELIM_NONE); | |||
} | |||
if ('\\' != p[0]) | |||
return(DELIM_NONE); | |||
if (0 == strcmp(p + 1, ".")) | |||
return(DELIM_CLOSE); | |||
if (0 == strcmp(p + 1, "fR|\\fP")) | |||
return(DELIM_MIDDLE); | |||
return(DELIM_NONE); | |||
} | |||
void | |||
mdoc_deroff(char **dest, const struct mdoc_node *n) | |||
{ | |||
char *cp; | |||
size_t sz; | |||
if (MDOC_TEXT != n->type) { | |||
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; | |||
} |