-/* $Id: roff.c,v 1.365 2019/04/21 23:51:21 schwarze Exp $ */
+/* $Id: roff.c,v 1.380 2021/10/04 14:19:14 schwarze Exp $ */
/*
+ * Copyright (c) 2010-2015, 2017-2021 Ingo Schwarze <schwarze@openbsd.org>
* Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2010-2015, 2017-2019 Ingo Schwarze <schwarze@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Implementation of the roff(7) parser for mandoc(1).
*/
#include "config.h"
static int roff_ccond(struct roff *, int, int);
static int roff_char(ROFF_ARGS);
static int roff_cond(ROFF_ARGS);
+static int roff_cond_checkend(ROFF_ARGS);
static int roff_cond_text(ROFF_ARGS);
static int roff_cond_sub(ROFF_ARGS);
static int roff_ds(ROFF_ARGS);
static int roff_ec(ROFF_ARGS);
static int roff_eo(ROFF_ARGS);
static int roff_eqndelim(struct roff *, struct buf *, int);
-static int roff_evalcond(struct roff *r, int, char *, int *);
+static int roff_evalcond(struct roff *, int, char *, int *);
static int roff_evalnum(struct roff *, int,
const char *, int *, int *, int);
static int roff_evalpar(struct roff *, int,
"Lk", "Mt", "Brq", "Bro",
"Brc", "%C", "Es", "En",
"Dx", "%Q", "%U", "Ta",
- NULL,
+ "Tg", NULL,
"TH", "SH", "SS", "TP",
"TQ",
"LP", "PP", "P", "IP",
roff_reset(struct roff *r)
{
roff_free1(r);
+ r->options |= MPARSE_COMMENT;
r->format = r->options & (MPARSE_MDOC | MPARSE_MAN);
r->control = '\0';
r->escape = '\\';
void
roff_free(struct roff *r)
{
- int i;
+ int i;
roff_free1(r);
for (i = 0; i < r->mstacksz; i++)
r = mandoc_calloc(1, sizeof(struct roff));
r->reqtab = roffhash_alloc(0, ROFF_RENAMED);
- r->options = options;
+ r->options = options | MPARSE_COMMENT;
r->format = options & (MPARSE_MDOC | MPARSE_MAN);
r->mstackpos = -1;
r->rstackpos = -1;
roff_man_free(struct roff_man *man)
{
roff_man_free1(man);
+ free(man->os_r);
free(man);
}
free(n->norm);
eqn_box_free(n->eqn);
free(n->string);
+ free(n->tag);
free(n);
}
roff_node_free(n);
}
+int
+roff_node_transparent(struct roff_node *n)
+{
+ if (n == NULL)
+ return 0;
+ if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
+ return 1;
+ return roff_tok_transparent(n->tok);
+}
+
+int
+roff_tok_transparent(enum roff_tok tok)
+{
+ switch (tok) {
+ case ROFF_ft:
+ case ROFF_ll:
+ case ROFF_mc:
+ case ROFF_po:
+ case ROFF_ta:
+ case MDOC_Db:
+ case MDOC_Es:
+ case MDOC_Sm:
+ case MDOC_Tg:
+ case MAN_DT:
+ case MAN_UC:
+ case MAN_PD:
+ case MAN_AT:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+struct roff_node *
+roff_node_child(struct roff_node *n)
+{
+ for (n = n->child; roff_node_transparent(n); n = n->next)
+ continue;
+ return n;
+}
+
+struct roff_node *
+roff_node_prev(struct roff_node *n)
+{
+ do {
+ n = n->prev;
+ } while (roff_node_transparent(n));
+ return n;
+}
+
+struct roff_node *
+roff_node_next(struct roff_node *n)
+{
+ do {
+ n = n->next;
+ } while (roff_node_transparent(n));
+ return n;
+}
+
void
deroff(char **dest, const struct roff_node *n)
{
char *cp;
size_t sz;
- if (n->type != ROFFT_TEXT) {
+ if (n->string == NULL) {
for (n = n->child; n != NULL; n = n->next)
deroff(dest, n);
return;
* in the syntax tree.
*/
- if (newesc != ASCII_ESC && r->format == 0) {
+ if (newesc != ASCII_ESC && r->options & MPARSE_COMMENT) {
while (*ep == ' ' || *ep == '\t')
ep--;
ep[1] = '\0';
roff_getarg(struct roff *r, char **cpp, int ln, int *pos)
{
struct buf buf;
- char *cp, *start;
+ char *cp, *start;
int newesc, pairs, quoted, white;
/* Quoting can only start with a new word. */
}
int
-roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs)
+roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs, size_t len)
{
enum roff_tok t;
int e;
ppos = pos = *offs;
+ if (len > 80 && r->tbl == NULL && r->eqn == NULL &&
+ (r->man->flags & ROFF_NOFILL) == 0 &&
+ strchr(" .\\", buf->buf[pos]) == NULL &&
+ buf->buf[pos] != r->control &&
+ strcspn(buf->buf, " ") < 80)
+ mandoc_msg(MANDOCERR_TEXT_LONG, ln, (int)len - 1,
+ "%.20s...", buf->buf + pos);
+
/* Handle in-line equation delimiters. */
if (r->tbl == NULL &&
roff_addtbl(r->man, ln, r->tbl);
return e;
}
- if ( ! ctl)
+ if ( ! ctl) {
+ r->options &= ~MPARSE_COMMENT;
return roff_parsetext(r, buf, pos, offs) | e;
+ }
/* Skip empty request lines. */
/* No scope is open. This is a new request or macro. */
+ r->options &= ~MPARSE_COMMENT;
spos = pos;
t = roff_parse(r, buf->buf, &pos, ln, ppos);
/* --- handling of request blocks ----------------------------------------- */
+/*
+ * Close a macro definition block or an "ignore" block.
+ */
static int
roff_cblock(ROFF_ARGS)
{
-
- /*
- * A block-close `..' should only be invoked as a child of an
- * ignore macro, otherwise raise a warning and just ignore it.
- */
+ int rr;
if (r->last == NULL) {
mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
switch (r->last->tok) {
case ROFF_am:
- /* ROFF_am1 is remapped to ROFF_am in roff_block(). */
case ROFF_ami:
case ROFF_de:
- /* ROFF_de1 is remapped to ROFF_de in roff_block(). */
case ROFF_dei:
case ROFF_ig:
break;
+ case ROFF_am1:
+ case ROFF_de1:
+ /* Remapped in roff_block(). */
+ abort();
default:
mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "..");
return ROFF_IGN;
}
+ roffnode_pop(r);
+ roffnode_cleanscope(r);
+
+ /*
+ * If a conditional block with braces is still open,
+ * check for "\}" block end markers.
+ */
+
+ if (r->last != NULL && r->last->endspan < 0) {
+ rr = 1; /* If arguments follow "\}", warn about them. */
+ roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
+ }
+
if (buf->buf[pos] != '\0')
mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
".. %s", buf->buf + pos);
- roffnode_pop(r);
- roffnode_cleanscope(r);
return ROFF_IGN;
-
}
/*
int inloop;
inloop = 0;
- while (r->last != NULL) {
+ while (r->last != NULL && r->last->endspan > 0) {
if (--r->last->endspan != 0)
break;
inloop += roffnode_pop(r);
}
/*
- * Handle the closing \} of a conditional block.
+ * Handle the closing "\}" of a conditional block.
* Apart from generating warnings, this only pops nodes.
* Return the number of loops ended.
*/
return ROFF_IGN;
}
+/*
+ * Check for a closing "\}" and handle it.
+ * In this function, the final "int *offs" argument is used for
+ * different purposes than elsewhere:
+ * Input: *offs == 0: caller wants to discard arguments following \}
+ * *offs == 1: caller wants to preserve text following \}
+ * Output: *offs = 0: tell caller to discard input line
+ * *offs = 1: tell caller to use input line
+ */
static int
-roff_cond_sub(ROFF_ARGS)
+roff_cond_checkend(ROFF_ARGS)
{
- struct roffnode *bl;
char *ep;
int endloop, irc, rr;
- enum roff_tok t;
irc = ROFF_IGN;
rr = r->last->rule;
irc |= endloop;
/*
- * If `\}' occurs on a macro line without a preceding macro,
- * drop the line completely.
+ * If "\}" occurs on a macro line without a preceding macro or
+ * a text line contains nothing else, drop the line completely.
*/
ep = buf->buf + pos;
- if (ep[0] == '\\' && ep[1] == '}')
+ if (ep[0] == '\\' && ep[1] == '}' && (ep[2] == '\0' || *offs == 0))
rr = 0;
/*
- * The closing delimiter `\}' rewinds the conditional scope
+ * The closing delimiter "\}" rewinds the conditional scope
* but is otherwise ignored when interpreting the line.
*/
while ((ep = strchr(ep, '\\')) != NULL) {
switch (ep[1]) {
case '}':
- memmove(ep, ep + 2, strlen(ep + 2) + 1);
+ if (ep[2] == '\0')
+ ep[0] = '\0';
+ else if (rr)
+ ep[1] = '&';
+ else
+ memmove(ep, ep + 2, strlen(ep + 2) + 1);
if (roff_ccond(r, ln, ep - buf->buf))
irc |= endloop;
break;
break;
}
}
+ *offs = rr;
+ return irc;
+}
+
+/*
+ * Parse and process a request or macro line in conditional scope.
+ */
+static int
+roff_cond_sub(ROFF_ARGS)
+{
+ struct roffnode *bl;
+ int irc, rr;
+ enum roff_tok t;
+
+ rr = 0; /* If arguments follow "\}", skip them. */
+ irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
+ t = roff_parse(r, buf->buf, &pos, ln, ppos);
+
+ /* For now, let high level macros abort .ce mode. */
+
+ if (roffce_node != NULL &&
+ (t == TOKEN_NONE || t == ROFF_Dd || t == ROFF_EQ ||
+ t == ROFF_TH || t == ROFF_TS)) {
+ r->man->last = roffce_node;
+ r->man->next = ROFF_NEXT_SIBLING;
+ roffce_lines = 0;
+ roffce_node = NULL;
+ }
/*
* Fully handle known macros when they are structurally
* required or when the conditional evaluated to true.
*/
- t = roff_parse(r, buf->buf, &pos, ln, ppos);
if (t == ROFF_break) {
if (irc & ROFF_LOOPMASK)
irc = ROFF_IGN | ROFF_LOOPEXIT;
return irc;
}
+/*
+ * Parse and process a text line in conditional scope.
+ */
static int
roff_cond_text(ROFF_ARGS)
{
- char *ep;
- int endloop, irc, rr;
+ int irc, rr;
- irc = ROFF_IGN;
- rr = r->last->rule;
- endloop = tok != ROFF_while ? ROFF_IGN :
- rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
- if (roffnode_cleanscope(r))
- irc |= endloop;
-
- /*
- * If `\}' occurs on a text line with neither preceding
- * nor following characters, drop the line completely.
- */
-
- ep = buf->buf + pos;
- if (strcmp(ep, "\\}") == 0)
- rr = 0;
-
- /*
- * The closing delimiter `\}' rewinds the conditional scope
- * but is otherwise ignored when interpreting the line.
- */
-
- while ((ep = strchr(ep, '\\')) != NULL) {
- switch (ep[1]) {
- case '}':
- memmove(ep, ep + 2, strlen(ep + 2) + 1);
- if (roff_ccond(r, ln, ep - buf->buf))
- irc |= endloop;
- break;
- case '\0':
- ++ep;
- break;
- default:
- ep += 2;
- break;
- }
- }
+ rr = 1; /* If arguments follow "\}", preserve them. */
+ irc = roff_cond_checkend(r, tok, buf, ln, ppos, pos, &rr);
if (rr)
irc |= ROFF_CONT;
return irc;
case ESCAPE_FONTITALIC:
case ESCAPE_FONTBOLD:
case ESCAPE_FONTBI:
- case ESCAPE_FONTCW:
+ case ESCAPE_FONTCR:
+ case ESCAPE_FONTCB:
+ case ESCAPE_FONTCI:
case ESCAPE_FONTPREV:
font++;
break;
r->mstacksz += 8;
}
ctx = r->mstack + r->mstackpos;
- ctx->argsz = 0;
ctx->argc = 0;
- ctx->argv = NULL;
/*
* Collect pointers to macro argument strings,