Return to mdoc.c CVS log | Up to [cvsweb.bsd.lv] / mandoc |
version 1.228, 2014/10/20 15:50:24 | version 1.262, 2017/04/24 23:06:18 | ||
---|---|---|---|
|
|
||
/* $Id$ */ | /* $Id$ */ | ||
/* | /* | ||
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> | ||
* Copyright (c) 2010, 2012, 2013, 2014 Ingo Schwarze <schwarze@openbsd.org> | * Copyright (c) 2010, 2012-2017 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 | ||
* copyright notice and this permission notice appear in all copies. | * copyright notice and this permission notice appear in all copies. | ||
* | * | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
|
|
||
#include <string.h> | #include <string.h> | ||
#include <time.h> | #include <time.h> | ||
#include "mdoc.h" | |||
#include "mandoc.h" | |||
#include "mandoc_aux.h" | #include "mandoc_aux.h" | ||
#include "libmdoc.h" | #include "mandoc.h" | ||
#include "roff.h" | |||
#include "mdoc.h" | |||
#include "libmandoc.h" | #include "libmandoc.h" | ||
#include "roff_int.h" | |||
#include "libmdoc.h" | |||
const char *const __mdoc_macronames[MDOC_MAX + 1] = { | |||
"Ap", "Dd", "Dt", "Os", | |||
"Sh", "Ss", "Pp", "D1", | |||
"Dl", "Bd", "Ed", "Bl", | |||
"El", "It", "Ad", "An", | |||
"Ar", "Cd", "Cm", "Dv", | |||
"Er", "Ev", "Ex", "Fa", | |||
"Fd", "Fl", "Fn", "Ft", | |||
"Ic", "In", "Li", "Nd", | |||
"Nm", "Op", "Ot", "Pa", | |||
"Rv", "St", "Va", "Vt", | |||
"Xr", "%A", "%B", "%D", | |||
"%I", "%J", "%N", "%O", | |||
"%P", "%R", "%T", "%V", | |||
"Ac", "Ao", "Aq", "At", | |||
"Bc", "Bf", "Bo", "Bq", | |||
"Bsx", "Bx", "Db", "Dc", | |||
"Do", "Dq", "Ec", "Ef", | |||
"Em", "Eo", "Fx", "Ms", | |||
"No", "Ns", "Nx", "Ox", | |||
"Pc", "Pf", "Po", "Pq", | |||
"Qc", "Ql", "Qo", "Qq", | |||
"Re", "Rs", "Sc", "So", | |||
"Sq", "Sm", "Sx", "Sy", | |||
"Tn", "Ux", "Xc", "Xo", | |||
"Fo", "Fc", "Oo", "Oc", | |||
"Bk", "Ek", "Bt", "Hf", | |||
"Fr", "Ud", "Lb", "Lp", | |||
"Lk", "Mt", "Brq", "Bro", | |||
"Brc", "%C", "Es", "En", | |||
"Dx", "%Q", "br", "sp", | |||
"%U", "Ta", "ll", "text", | |||
}; | |||
const char *const __mdoc_argnames[MDOC_ARG_MAX] = { | const char *const __mdoc_argnames[MDOC_ARG_MAX] = { | ||
"split", "nosplit", "ragged", | "split", "nosplit", "ragged", | ||
"unfilled", "literal", "file", | "unfilled", "literal", "file", | ||
|
|
||
"width", "compact", "std", | "width", "compact", "std", | ||
"filled", "words", "emphasis", | "filled", "words", "emphasis", | ||
"symbolic", "nested", "centered" | "symbolic", "nested", "centered" | ||
}; | }; | ||
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 int mdoc_ptext(struct roff_man *, int, char *, int); | ||
static void mdoc_node_unlink(struct mdoc *, | static int mdoc_pmacro(struct roff_man *, int, char *, int); | ||
struct mdoc_node *); | |||
static void mdoc_free1(struct mdoc *); | |||
static void mdoc_alloc1(struct mdoc *); | |||
static struct mdoc_node *node_alloc(struct mdoc *, int, int, | |||
enum mdoct, enum mdoc_type); | |||
static int node_append(struct mdoc *, | |||
struct mdoc_node *); | |||
static int mdoc_ptext(struct mdoc *, int, char *, int); | |||
static int mdoc_pmacro(struct mdoc *, int, char *, int); | |||
const struct mdoc_node * | |||
mdoc_node(const struct mdoc *mdoc) | |||
{ | |||
return(mdoc->first); | |||
} | |||
const struct mdoc_meta * | |||
mdoc_meta(const struct mdoc *mdoc) | |||
{ | |||
return(&mdoc->meta); | |||
} | |||
/* | /* | ||
* Frees volatile resources (parse tree, meta-data, fields). | |||
*/ | |||
static void | |||
mdoc_free1(struct mdoc *mdoc) | |||
{ | |||
if (mdoc->first) | |||
mdoc_node_delete(mdoc, mdoc->first); | |||
free(mdoc->meta.msec); | |||
free(mdoc->meta.vol); | |||
free(mdoc->meta.arch); | |||
free(mdoc->meta.date); | |||
free(mdoc->meta.title); | |||
free(mdoc->meta.os); | |||
free(mdoc->meta.name); | |||
} | |||
/* | |||
* Allocate all volatile resources (parse tree, meta-data, fields). | |||
*/ | |||
static void | |||
mdoc_alloc1(struct mdoc *mdoc) | |||
{ | |||
memset(&mdoc->meta, 0, sizeof(struct mdoc_meta)); | |||
mdoc->flags = 0; | |||
mdoc->lastnamed = mdoc->lastsec = SEC_NONE; | |||
mdoc->last = mandoc_calloc(1, sizeof(struct mdoc_node)); | |||
mdoc->first = mdoc->last; | |||
mdoc->last->type = MDOC_ROOT; | |||
mdoc->last->tok = MDOC_MAX; | |||
mdoc->next = MDOC_NEXT_CHILD; | |||
} | |||
/* | |||
* Free up volatile resources (see mdoc_free1()) then re-initialises the | |||
* data with mdoc_alloc1(). After invocation, parse data has been reset | |||
* and the parser is ready for re-invocation on a new tree; however, | |||
* cross-parse non-volatile data is kept intact. | |||
*/ | |||
void | |||
mdoc_reset(struct mdoc *mdoc) | |||
{ | |||
mdoc_free1(mdoc); | |||
mdoc_alloc1(mdoc); | |||
} | |||
/* | |||
* Completely free up all volatile and non-volatile parse resources. | |||
* After invocation, the pointer is no longer usable. | |||
*/ | |||
void | |||
mdoc_free(struct mdoc *mdoc) | |||
{ | |||
mdoc_free1(mdoc); | |||
free(mdoc); | |||
} | |||
/* | |||
* Allocate volatile and non-volatile parse resources. | |||
*/ | |||
struct mdoc * | |||
mdoc_alloc(struct roff *roff, struct mparse *parse, | |||
const char *defos, int quick) | |||
{ | |||
struct mdoc *p; | |||
p = mandoc_calloc(1, sizeof(struct mdoc)); | |||
p->parse = parse; | |||
p->defos = defos; | |||
p->quick = quick; | |||
p->roff = roff; | |||
mdoc_hash_init(); | |||
mdoc_alloc1(p); | |||
return(p); | |||
} | |||
int | |||
mdoc_endparse(struct mdoc *mdoc) | |||
{ | |||
return(mdoc_macroend(mdoc)); | |||
} | |||
int | |||
mdoc_addeqn(struct mdoc *mdoc, const struct eqn *ep) | |||
{ | |||
struct mdoc_node *n; | |||
n = node_alloc(mdoc, ep->ln, ep->pos, MDOC_MAX, MDOC_EQN); | |||
n->eqn = ep; | |||
if (ep->ln > mdoc->last->line) | |||
n->flags |= MDOC_LINE; | |||
if ( ! node_append(mdoc, n)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_SIBLING; | |||
return(1); | |||
} | |||
int | |||
mdoc_addspan(struct mdoc *mdoc, const struct tbl_span *sp) | |||
{ | |||
struct mdoc_node *n; | |||
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 | * Main parse routine. Parses a single line -- really just hands off to | ||
* the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). | * the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). | ||
*/ | */ | ||
int | int | ||
mdoc_parseln(struct mdoc *mdoc, int ln, char *buf, int offs) | mdoc_parseln(struct roff_man *mdoc, int ln, char *buf, int offs) | ||
{ | { | ||
if (mdoc->last->type != MDOC_EQN || ln > mdoc->last->line) | if (mdoc->last->type != ROFFT_EQN || ln > mdoc->last->line) | ||
mdoc->flags |= MDOC_NEWLINE; | mdoc->flags |= MDOC_NEWLINE; | ||
/* | /* | ||
|
|
||
else | else | ||
mdoc->flags &= ~MDOC_SYNOPSIS; | mdoc->flags &= ~MDOC_SYNOPSIS; | ||
return(roff_getcontrol(mdoc->roff, buf, &offs) ? | return roff_getcontrol(mdoc->roff, buf, &offs) ? | ||
mdoc_pmacro(mdoc, ln, buf, offs) : | mdoc_pmacro(mdoc, ln, buf, offs) : | ||
mdoc_ptext(mdoc, ln, buf, offs)); | mdoc_ptext(mdoc, ln, buf, offs); | ||
} | } | ||
int | void | ||
mdoc_macro(MACRO_PROT_ARGS) | mdoc_macro(MACRO_PROT_ARGS) | ||
{ | { | ||
assert(tok < MDOC_MAX); | assert(tok >= MDOC_Dd && tok < MDOC_MAX); | ||
(*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf); | |||
if (mdoc->flags & MDOC_PBODY) { | |||
if (tok == MDOC_Dt) { | |||
mandoc_vmsg(MANDOCERR_DT_LATE, | |||
mdoc->parse, line, ppos, | |||
"Dt %s", buf + *pos); | |||
return(1); | |||
} | |||
} else if ( ! (mdoc_macros[tok].flags & MDOC_PROLOGUE)) { | |||
if (mdoc->meta.title == NULL) { | |||
mandoc_vmsg(MANDOCERR_DT_NOTITLE, | |||
mdoc->parse, line, ppos, "%s %s", | |||
mdoc_macronames[tok], buf + *pos); | |||
mdoc->meta.title = mandoc_strdup("UNTITLED"); | |||
} | |||
if (NULL == mdoc->meta.vol) | |||
mdoc->meta.vol = mandoc_strdup("LOCAL"); | |||
mdoc->flags |= MDOC_PBODY; | |||
} | |||
return((*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf)); | |||
} | } | ||
void | |||
static int | mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok) | ||
node_append(struct mdoc *mdoc, struct mdoc_node *p) | |||
{ | { | ||
struct roff_node *p; | |||
assert(mdoc->last); | p = roff_node_alloc(mdoc, line, pos, ROFFT_TAIL, tok); | ||
assert(mdoc->first); | roff_node_append(mdoc, p); | ||
assert(MDOC_ROOT != p->type); | mdoc->next = ROFF_NEXT_CHILD; | ||
switch (mdoc->next) { | |||
case MDOC_NEXT_SIBLING: | |||
mdoc->last->next = p; | |||
p->prev = mdoc->last; | |||
p->parent = mdoc->last->parent; | |||
break; | |||
case MDOC_NEXT_CHILD: | |||
mdoc->last->child = p; | |||
p->parent = mdoc->last; | |||
break; | |||
default: | |||
abort(); | |||
/* 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: | |||
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)) | |||
return(0); | |||
switch (p->type) { | |||
case MDOC_HEAD: | |||
assert(MDOC_BLOCK == p->parent->type); | |||
p->parent->head = p; | |||
break; | |||
case MDOC_TAIL: | |||
assert(MDOC_BLOCK == p->parent->type); | |||
p->parent->tail = p; | |||
break; | |||
case MDOC_BODY: | |||
if (p->end) | |||
break; | |||
assert(MDOC_BLOCK == p->parent->type); | |||
p->parent->body = p; | |||
break; | |||
default: | |||
break; | |||
} | |||
mdoc->last = p; | |||
switch (p->type) { | |||
case MDOC_TBL: | |||
/* FALLTHROUGH */ | |||
case MDOC_TEXT: | |||
if ( ! mdoc_valid_post(mdoc)) | |||
return(0); | |||
break; | |||
default: | |||
break; | |||
} | |||
return(1); | |||
} | } | ||
static struct mdoc_node * | struct roff_node * | ||
node_alloc(struct mdoc *mdoc, int line, int pos, | mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos, | ||
enum mdoct tok, enum mdoc_type type) | enum roff_tok tok, struct roff_node *body) | ||
{ | { | ||
struct mdoc_node *p; | struct roff_node *p; | ||
p = mandoc_calloc(1, sizeof(struct mdoc_node)); | body->flags |= NODE_ENDED; | ||
p->sec = mdoc->lastsec; | body->parent->flags |= NODE_ENDED; | ||
p->line = line; | p = roff_node_alloc(mdoc, line, pos, ROFFT_BODY, tok); | ||
p->pos = pos; | p->body = body; | ||
p->lastline = line; | |||
p->tok = tok; | |||
p->type = type; | |||
/* 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; | |||
mdoc->flags &= ~MDOC_NEWLINE; | |||
return(p); | |||
} | |||
int | |||
mdoc_tail_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok) | |||
{ | |||
struct mdoc_node *p; | |||
p = node_alloc(mdoc, line, pos, tok, MDOC_TAIL); | |||
if ( ! node_append(mdoc, p)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_CHILD; | |||
return(1); | |||
} | |||
int | |||
mdoc_head_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok) | |||
{ | |||
struct mdoc_node *p; | |||
assert(mdoc->first); | |||
assert(mdoc->last); | |||
p = node_alloc(mdoc, line, pos, tok, MDOC_HEAD); | |||
if ( ! node_append(mdoc, p)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_CHILD; | |||
return(1); | |||
} | |||
int | |||
mdoc_body_alloc(struct mdoc *mdoc, int line, int pos, enum mdoct tok) | |||
{ | |||
struct mdoc_node *p; | |||
p = node_alloc(mdoc, line, pos, tok, MDOC_BODY); | |||
if ( ! node_append(mdoc, p)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_CHILD; | |||
return(1); | |||
} | |||
int | |||
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->norm = body->norm; | ||
p->end = end; | p->end = ENDBODY_SPACE; | ||
if ( ! node_append(mdoc, p)) | roff_node_append(mdoc, p); | ||
return(0); | mdoc->next = ROFF_NEXT_SIBLING; | ||
mdoc->next = MDOC_NEXT_SIBLING; | return p; | ||
return(1); | |||
} | } | ||
int | struct roff_node * | ||
mdoc_block_alloc(struct mdoc *mdoc, int line, int pos, | mdoc_block_alloc(struct roff_man *mdoc, int line, int pos, | ||
enum mdoct tok, struct mdoc_arg *args) | enum roff_tok tok, struct mdoc_arg *args) | ||
{ | { | ||
struct mdoc_node *p; | struct roff_node *p; | ||
p = node_alloc(mdoc, line, pos, tok, MDOC_BLOCK); | p = roff_node_alloc(mdoc, line, pos, ROFFT_BLOCK, tok); | ||
p->args = args; | p->args = args; | ||
if (p->args) | if (p->args) | ||
(args->refcnt)++; | (args->refcnt)++; | ||
switch (tok) { | switch (tok) { | ||
case MDOC_Bd: | case MDOC_Bd: | ||
/* FALLTHROUGH */ | |||
case MDOC_Bf: | case MDOC_Bf: | ||
/* FALLTHROUGH */ | |||
case MDOC_Bl: | case MDOC_Bl: | ||
/* FALLTHROUGH */ | |||
case MDOC_En: | case MDOC_En: | ||
/* FALLTHROUGH */ | |||
case MDOC_Rs: | case MDOC_Rs: | ||
p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); | p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); | ||
break; | break; | ||
default: | default: | ||
break; | break; | ||
} | } | ||
roff_node_append(mdoc, p); | |||
if ( ! node_append(mdoc, p)) | mdoc->next = ROFF_NEXT_CHILD; | ||
return(0); | return p; | ||
mdoc->next = MDOC_NEXT_CHILD; | |||
return(1); | |||
} | } | ||
int | void | ||
mdoc_elem_alloc(struct mdoc *mdoc, int line, int pos, | mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos, | ||
enum mdoct tok, struct mdoc_arg *args) | enum roff_tok tok, struct mdoc_arg *args) | ||
{ | { | ||
struct mdoc_node *p; | struct roff_node *p; | ||
p = node_alloc(mdoc, line, pos, tok, MDOC_ELEM); | p = roff_node_alloc(mdoc, line, pos, ROFFT_ELEM, tok); | ||
p->args = args; | p->args = args; | ||
if (p->args) | if (p->args) | ||
(args->refcnt)++; | (args->refcnt)++; | ||
|
|
||
default: | default: | ||
break; | break; | ||
} | } | ||
roff_node_append(mdoc, p); | |||
if ( ! node_append(mdoc, p)) | mdoc->next = ROFF_NEXT_CHILD; | ||
return(0); | |||
mdoc->next = MDOC_NEXT_CHILD; | |||
return(1); | |||
} | } | ||
int | |||
mdoc_word_alloc(struct mdoc *mdoc, int line, int pos, const char *p) | |||
{ | |||
struct mdoc_node *n; | |||
n = node_alloc(mdoc, line, pos, MDOC_MAX, MDOC_TEXT); | |||
n->string = roff_strdup(mdoc->roff, p); | |||
if ( ! node_append(mdoc, n)) | |||
return(0); | |||
mdoc->next = MDOC_NEXT_SIBLING; | |||
return(1); | |||
} | |||
void | void | ||
mdoc_word_append(struct mdoc *mdoc, const char *p) | mdoc_node_relink(struct roff_man *mdoc, struct roff_node *p) | ||
{ | { | ||
struct mdoc_node *n; | |||
char *addstr, *newstr; | |||
n = mdoc->last; | roff_node_unlink(mdoc, p); | ||
addstr = roff_strdup(mdoc->roff, p); | p->prev = p->next = NULL; | ||
mandoc_asprintf(&newstr, "%s %s", n->string, addstr); | roff_node_append(mdoc, p); | ||
free(addstr); | |||
free(n->string); | |||
n->string = newstr; | |||
mdoc->next = MDOC_NEXT_SIBLING; | |||
} | } | ||
static void | |||
mdoc_node_free(struct mdoc_node *p) | |||
{ | |||
if (MDOC_BLOCK == p->type || MDOC_ELEM == p->type) | |||
free(p->norm); | |||
if (p->string) | |||
free(p->string); | |||
if (p->args) | |||
mdoc_argv_free(p->args); | |||
free(p); | |||
} | |||
static void | |||
mdoc_node_unlink(struct mdoc *mdoc, 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 (mdoc && mdoc->last == n) { | |||
if (n->prev) { | |||
mdoc->last = n->prev; | |||
mdoc->next = MDOC_NEXT_SIBLING; | |||
} else { | |||
mdoc->last = n->parent; | |||
mdoc->next = MDOC_NEXT_CHILD; | |||
} | |||
} | |||
if (mdoc && mdoc->first == n) | |||
mdoc->first = NULL; | |||
} | |||
void | |||
mdoc_node_delete(struct mdoc *mdoc, struct mdoc_node *p) | |||
{ | |||
while (p->child) { | |||
assert(p->nchild); | |||
mdoc_node_delete(mdoc, p->child); | |||
} | |||
assert(0 == p->nchild); | |||
mdoc_node_unlink(mdoc, 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)); | |||
} | |||
/* | /* | ||
* Parse free-form text, that is, a line that does not begin with the | * Parse free-form text, that is, a line that does not begin with the | ||
* control character. | * control character. | ||
*/ | */ | ||
static int | static int | ||
mdoc_ptext(struct mdoc *mdoc, int line, char *buf, int offs) | mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs) | ||
{ | { | ||
struct roff_node *n; | |||
char *c, *ws, *end; | char *c, *ws, *end; | ||
struct mdoc_node *n; | |||
assert(mdoc->last); | |||
n = mdoc->last; | n = mdoc->last; | ||
/* | /* | ||
* Divert directly to list processing if we're encountering a | * If a column list contains plain text, assume an implicit item | ||
* columnar MDOC_BLOCK with or without a prior MDOC_BLOCK entry | * macro. This can happen one or more times at the beginning | ||
* (a MDOC_BODY means it's already open, in which case we should | * of such a list, intermixed with non-It mdoc macros and with | ||
* process within its context in the normal way). | * nodes generated on the roff level, for example by tbl. | ||
*/ | */ | ||
if (MDOC_Bl == n->tok && MDOC_BODY == n->type && | if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && | ||
LIST_column == n->norm->Bl.type) { | n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || | ||
/* `Bl' is open without any children. */ | (n->parent != NULL && n->parent->tok == MDOC_Bl && | ||
n->parent->norm->Bl.type == LIST_column)) { | |||
mdoc->flags |= MDOC_FREECOL; | mdoc->flags |= MDOC_FREECOL; | ||
return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf)); | mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf); | ||
return 1; | |||
} | } | ||
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. */ | |||
mdoc->flags |= MDOC_FREECOL; | |||
return(mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf)); | |||
} | |||
/* | /* | ||
* Search for the beginning of unescaped trailing whitespace (ws) | * Search for the beginning of unescaped trailing whitespace (ws) | ||
* and for the first character not to be output (end). | * and for the first character not to be output (end). | ||
|
|
||
mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse, | mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse, | ||
line, (int)(ws-buf), NULL); | line, (int)(ws-buf), NULL); | ||
if ('\0' == buf[offs] && ! (MDOC_LITERAL & mdoc->flags)) { | if (buf[offs] == '\0' && ! (mdoc->flags & MDOC_LITERAL)) { | ||
mandoc_msg(MANDOCERR_FI_BLANK, mdoc->parse, | mandoc_msg(MANDOCERR_FI_BLANK, mdoc->parse, | ||
line, (int)(c - buf), NULL); | line, (int)(c - buf), NULL); | ||
|
|
||
* blank lines aren't allowed, but enough manuals assume this | * blank lines aren't allowed, but enough manuals assume this | ||
* behaviour that we want to work around it. | * behaviour that we want to work around it. | ||
*/ | */ | ||
if ( ! mdoc_elem_alloc(mdoc, line, offs, MDOC_sp, NULL)) | roff_elem_alloc(mdoc, line, offs, MDOC_sp); | ||
return(0); | mdoc->last->flags |= NODE_VALID | NODE_ENDED; | ||
mdoc->next = ROFF_NEXT_SIBLING; | |||
mdoc->next = MDOC_NEXT_SIBLING; | return 1; | ||
return(mdoc_valid_post(mdoc)); | |||
} | } | ||
if ( ! mdoc_word_alloc(mdoc, line, offs, buf+offs)) | roff_word_alloc(mdoc, line, offs, buf+offs); | ||
return(0); | |||
if (MDOC_LITERAL & mdoc->flags) | if (mdoc->flags & MDOC_LITERAL) | ||
return(1); | return 1; | ||
/* | /* | ||
* End-of-sentence check. If the last character is an unescaped | * End-of-sentence check. If the last character is an unescaped | ||
|
|
||
assert(buf < end); | assert(buf < end); | ||
if (mandoc_eos(buf+offs, (size_t)(end-buf-offs))) | if (mandoc_eos(buf+offs, (size_t)(end-buf-offs))) | ||
mdoc->last->flags |= MDOC_EOS; | mdoc->last->flags |= NODE_EOS; | ||
return(1); | for (c = buf + offs; c != NULL; c = strchr(c + 1, '.')) { | ||
if (c - buf < offs + 2) | |||
continue; | |||
if (end - c < 4) | |||
break; | |||
if (isalpha((unsigned char)c[-2]) && | |||
isalpha((unsigned char)c[-1]) && | |||
c[1] == ' ' && | |||
isupper((unsigned char)(c[2] == ' ' ? c[3] : c[2])) && | |||
(c[-2] != 'n' || c[-1] != 'c') && | |||
(c[-2] != 'v' || c[-1] != 's')) | |||
mandoc_msg(MANDOCERR_EOS, mdoc->parse, | |||
line, (int)(c - buf), NULL); | |||
} | |||
return 1; | |||
} | } | ||
/* | /* | ||
|
|
||
* character. | * character. | ||
*/ | */ | ||
static int | static int | ||
mdoc_pmacro(struct mdoc *mdoc, int ln, char *buf, int offs) | mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs) | ||
{ | { | ||
enum mdoct tok; | struct roff_node *n; | ||
const char *cp; | |||
enum roff_tok tok; | |||
int i, sv; | int i, sv; | ||
char mac[5]; | char mac[5]; | ||
struct mdoc_node *n; | |||
sv = offs; | sv = offs; | ||
/* | /* | ||
* 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. | * Stop when a space, tab, escape, or eoln is encountered. | ||
*/ | */ | ||
i = 0; | i = 0; | ||
while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] && | while (i < 4 && strchr(" \t\\", buf[offs]) == NULL) | ||
'\t' != buf[offs]) | |||
mac[i++] = buf[offs++]; | mac[i++] = buf[offs++]; | ||
mac[i] = '\0'; | mac[i] = '\0'; | ||
tok = (i > 1 && i < 4) ? mdoc_hash_find(mac) : MDOC_MAX; | tok = (i > 1 && i < 4) ? mdoc_hash_find(mac) : TOKEN_NONE; | ||
if (MDOC_MAX == tok) { | if (tok == TOKEN_NONE) { | ||
mandoc_msg(MANDOCERR_MACRO, mdoc->parse, | mandoc_msg(MANDOCERR_MACRO, mdoc->parse, | ||
ln, sv, buf + sv - 1); | ln, sv, buf + sv - 1); | ||
return(1); | return 1; | ||
} | } | ||
/* Disregard the first trailing tab, if applicable. */ | /* Skip a leading escape sequence or tab. */ | ||
if ('\t' == buf[offs]) | switch (buf[offs]) { | ||
case '\\': | |||
cp = buf + offs + 1; | |||
mandoc_escape(&cp, NULL, NULL); | |||
offs = cp - buf; | |||
break; | |||
case '\t': | |||
offs++; | offs++; | ||
break; | |||
default: | |||
break; | |||
} | |||
/* Jump to the next non-whitespace word. */ | /* Jump to the next non-whitespace word. */ | ||
while (buf[offs] && ' ' == buf[offs]) | while (buf[offs] == ' ') | ||
offs++; | offs++; | ||
/* | /* | ||
|
|
||
* into macro processing. | * into macro processing. | ||
*/ | */ | ||
if (NULL == mdoc->last || MDOC_It == tok || MDOC_El == tok) | |||
return(mdoc_macro(mdoc, tok, ln, sv, &offs, buf)); | |||
n = mdoc->last; | n = mdoc->last; | ||
assert(mdoc->last); | if (n == NULL || tok == MDOC_It || tok == MDOC_El) { | ||
mdoc_macro(mdoc, tok, ln, sv, &offs, buf); | |||
/* | return 1; | ||
* 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) { | |||
mdoc->flags |= MDOC_FREECOL; | |||
return(mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf)); | |||
} | } | ||
/* | /* | ||
* If we're following a block-level `It' within a `Bl -column' | * If a column list contains a non-It macro, assume an implicit | ||
* context (perhaps opened in the above block or in ptext()), | * item macro. This can happen one or more times at the | ||
* then open an `It' block context around the parsed macro. | * beginning of such a list, intermixed with text lines and | ||
* with nodes generated on the roff level, for example by tbl. | |||
*/ | */ | ||
if (MDOC_It == n->tok && MDOC_BLOCK == n->type && | if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && | ||
NULL != n->parent && | n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || | ||
MDOC_Bl == n->parent->tok && | (n->parent != NULL && n->parent->tok == MDOC_Bl && | ||
LIST_column == n->parent->norm->Bl.type) { | n->parent->norm->Bl.type == LIST_column)) { | ||
mdoc->flags |= MDOC_FREECOL; | mdoc->flags |= MDOC_FREECOL; | ||
return(mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf)); | mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf); | ||
return 1; | |||
} | } | ||
/* Normal processing of a macro. */ | /* Normal processing of a macro. */ | ||
if ( ! mdoc_macro(mdoc, tok, ln, sv, &offs, buf)) | mdoc_macro(mdoc, tok, ln, sv, &offs, buf); | ||
return(0); | |||
/* In quick mode (for mandocdb), abort after the NAME section. */ | /* In quick mode (for mandocdb), abort after the NAME section. */ | ||
if (mdoc->quick && MDOC_Sh == tok && | if (mdoc->quick && MDOC_Sh == tok && | ||
SEC_NAME != mdoc->last->sec) | SEC_NAME != mdoc->last->sec) | ||
return(2); | return 2; | ||
return(1); | return 1; | ||
} | } | ||
enum mdelim | enum mdelim | ||
|
|
||
{ | { | ||
if ('\0' == p[0]) | if ('\0' == p[0]) | ||
return(DELIM_NONE); | return DELIM_NONE; | ||
if ('\0' == p[1]) | if ('\0' == p[1]) | ||
switch (p[0]) { | switch (p[0]) { | ||
case '(': | case '(': | ||
/* FALLTHROUGH */ | |||
case '[': | case '[': | ||
return(DELIM_OPEN); | return DELIM_OPEN; | ||
case '|': | case '|': | ||
return(DELIM_MIDDLE); | return DELIM_MIDDLE; | ||
case '.': | case '.': | ||
/* FALLTHROUGH */ | |||
case ',': | case ',': | ||
/* FALLTHROUGH */ | |||
case ';': | case ';': | ||
/* FALLTHROUGH */ | |||
case ':': | case ':': | ||
/* FALLTHROUGH */ | |||
case '?': | case '?': | ||
/* FALLTHROUGH */ | |||
case '!': | case '!': | ||
/* FALLTHROUGH */ | |||
case ')': | case ')': | ||
/* FALLTHROUGH */ | |||
case ']': | case ']': | ||
return(DELIM_CLOSE); | return DELIM_CLOSE; | ||
default: | default: | ||
return(DELIM_NONE); | return DELIM_NONE; | ||
} | } | ||
if ('\\' != p[0]) | if ('\\' != p[0]) | ||
return(DELIM_NONE); | return DELIM_NONE; | ||
if (0 == strcmp(p + 1, ".")) | if (0 == strcmp(p + 1, ".")) | ||
return(DELIM_CLOSE); | return DELIM_CLOSE; | ||
if (0 == strcmp(p + 1, "fR|\\fP")) | if (0 == strcmp(p + 1, "fR|\\fP")) | ||
return(DELIM_MIDDLE); | return DELIM_MIDDLE; | ||
return(DELIM_NONE); | return DELIM_NONE; | ||
} | } | ||
void | void | ||
mdoc_deroff(char **dest, const struct mdoc_node *n) | mdoc_validate(struct roff_man *mdoc) | ||
{ | { | ||
char *cp; | |||
size_t sz; | |||
if (MDOC_TEXT != n->type) { | mdoc->last = mdoc->first; | ||
for (n = n->child; n; n = n->next) | mdoc_node_validate(mdoc); | ||
mdoc_deroff(dest, n); | mdoc_state_reset(mdoc); | ||
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; | |||
} | } |