-/* $Id: roff.c,v 1.354 2018/12/20 03:41:54 schwarze Exp $ */
+/* $Id: roff.c,v 1.377 2021/06/27 17:57:55 schwarze Exp $ */
/*
+ * Copyright (c) 2010-2015, 2017-2020 Ingo Schwarze <schwarze@openbsd.org>
* Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2010-2015, 2017, 2018 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"
#include "tbl_parse.h"
#include "eqn_parse.h"
+/*
+ * ASCII_ESC is used to signal from roff_getarg() to roff_expand()
+ * that an escape sequence resulted from copy-in processing and
+ * needs to be checked or interpolated. As it is used nowhere
+ * else, it is defined here rather than in a header file.
+ */
+#define ASCII_ESC 27
+
/* Maximum number of string expansions per line, to break infinite loops. */
#define EXPAND_LIMIT 1000
char escape; /* escape character */
};
+/*
+ * A macro definition, condition, or ignored block.
+ */
struct roffnode {
enum roff_tok tok; /* type of node */
struct roffnode *parent; /* up one in stack */
int line; /* parse line */
int col; /* parse col */
char *name; /* node name, e.g. macro name */
- char *end; /* end-rules: custom token */
- int endspan; /* end-rules: next-line or infty */
- int rule; /* current evaluation rule */
+ char *end; /* custom end macro of the block */
+ int endspan; /* scope to: 1=eol 2=next line -1=\} */
+ int rule; /* content is: 1=evaluated 0=skipped */
};
#define ROFF_ARGS struct roff *r, /* parse ctx */ \
static int roff_block(ROFF_ARGS);
static int roff_block_text(ROFF_ARGS);
static int roff_block_sub(ROFF_ARGS);
-static int roff_br(ROFF_ARGS);
+static int roff_break(ROFF_ARGS);
static int roff_cblock(ROFF_ARGS);
static int roff_cc(ROFF_ARGS);
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,
const char *, int *, int *, int);
static int roff_evalstrcond(const char *, int *);
+static int roff_expand(struct roff *, struct buf *,
+ int, int, char);
static void roff_free1(struct roff *);
static void roff_freereg(struct roffreg *);
static void roff_freestr(struct roffkv *);
static void roff_man_alloc1(struct roff_man *);
static void roff_man_free1(struct roff_man *);
static int roff_manyarg(ROFF_ARGS);
+static int roff_noarg(ROFF_ARGS);
static int roff_nop(ROFF_ARGS);
static int roff_nr(ROFF_ARGS);
static int roff_onearg(ROFF_ARGS);
static int roff_parsetext(struct roff *, struct buf *,
int, int *);
static int roff_renamed(ROFF_ARGS);
-static int roff_res(struct roff *, struct buf *, int, int);
static int roff_return(ROFF_ARGS);
static int roff_rm(ROFF_ARGS);
static int roff_rn(ROFF_ARGS);
#define ROFFNUM_WHITE (1 << 1) /* Skip whitespace in roff_evalnum(). */
const char *__roff_name[MAN_MAX + 1] = {
- "br", "ce", "ft", "ll",
- "mc", "po", "rj", "sp",
+ "br", "ce", "fi", "ft",
+ "ll", "mc", "nf",
+ "po", "rj", "sp",
"ta", "ti", NULL,
"ab", "ad", "af", "aln",
"als", "am", "am1", "ami",
"Lk", "Mt", "Brq", "Bro",
"Brc", "%C", "Es", "En",
"Dx", "%Q", "%U", "Ta",
- NULL,
+ "Tg", NULL,
"TH", "SH", "SS", "TP",
"TQ",
"LP", "PP", "P", "IP",
"HP", "SM", "SB", "BI",
"IB", "BR", "RB", "R",
"B", "I", "IR", "RI",
- "nf", "fi",
"RE", "RS", "DT", "UC",
"PD", "AT", "in",
"SY", "YS", "OP",
const char *const *roff_name = __roff_name;
static struct roffmac roffs[TOKEN_NONE] = {
- { roff_br, NULL, NULL, 0 }, /* br */
+ { roff_noarg, NULL, NULL, 0 }, /* br */
{ roff_onearg, NULL, NULL, 0 }, /* ce */
+ { roff_noarg, NULL, NULL, 0 }, /* fi */
{ roff_onearg, NULL, NULL, 0 }, /* ft */
{ roff_onearg, NULL, NULL, 0 }, /* ll */
{ roff_onearg, NULL, NULL, 0 }, /* mc */
+ { roff_noarg, NULL, NULL, 0 }, /* nf */
{ roff_onearg, NULL, NULL, 0 }, /* po */
{ roff_onearg, NULL, NULL, 0 }, /* rj */
{ roff_onearg, NULL, NULL, 0 }, /* sp */
{ roff_unsupp, NULL, NULL, 0 }, /* boxa */
{ roff_line_ignore, NULL, NULL, 0 }, /* bp */
{ roff_unsupp, NULL, NULL, 0 }, /* BP */
- { roff_unsupp, NULL, NULL, 0 }, /* break */
+ { roff_break, NULL, NULL, 0 }, /* break */
{ roff_line_ignore, NULL, NULL, 0 }, /* breakchar */
{ roff_line_ignore, NULL, NULL, 0 }, /* brnl */
- { roff_br, NULL, NULL, 0 }, /* brp */
+ { roff_noarg, NULL, NULL, 0 }, /* brp */
{ roff_line_ignore, NULL, NULL, 0 }, /* brpnl */
{ roff_unsupp, NULL, NULL, 0 }, /* c2 */
{ roff_cc, NULL, NULL, 0 }, /* cc */
/*
* Pop the current node off of the stack of roff instructions currently
- * pending.
+ * pending. Return 1 if it is a loop or 0 otherwise.
*/
static int
roffnode_pop(struct roff *r)
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;
static void
roff_man_free1(struct roff_man *man)
{
-
- if (man->first != NULL)
- roff_node_delete(man, man->first);
+ if (man->meta.first != NULL)
+ roff_node_delete(man, man->meta.first);
free(man->meta.msec);
free(man->meta.vol);
free(man->meta.os);
free(man->meta.title);
free(man->meta.name);
free(man->meta.date);
+ free(man->meta.sodest);
}
-static void
-roff_man_alloc1(struct roff_man *man)
+void
+roff_state_reset(struct roff_man *man)
{
-
- memset(&man->meta, 0, sizeof(man->meta));
- man->first = mandoc_calloc(1, sizeof(*man->first));
- man->first->type = ROFFT_ROOT;
- man->last = man->first;
+ man->last = man->meta.first;
man->last_es = NULL;
man->flags = 0;
- man->macroset = MACROSET_NONE;
man->lastsec = man->lastnamed = SEC_NONE;
man->next = ROFF_NEXT_CHILD;
+ roff_setreg(man->roff, "nS", 0, '=');
+}
+
+static void
+roff_man_alloc1(struct roff_man *man)
+{
+ memset(&man->meta, 0, sizeof(man->meta));
+ man->meta.first = mandoc_calloc(1, sizeof(*man->meta.first));
+ man->meta.first->type = ROFFT_ROOT;
+ man->meta.macroset = MACROSET_NONE;
+ roff_state_reset(man);
}
void
roff_man_reset(struct roff_man *man)
{
-
roff_man_free1(man);
roff_man_alloc1(man);
}
void
roff_man_free(struct roff_man *man)
{
-
roff_man_free1(man);
free(man);
}
n->flags |= NODE_SYNPRETTY;
else
n->flags &= ~NODE_SYNPRETTY;
+ if ((man->flags & (ROFF_NOFILL | ROFF_NONOFILL)) == ROFF_NOFILL)
+ n->flags |= NODE_NOFILL;
+ else
+ n->flags &= ~NODE_NOFILL;
if (man->flags & MDOC_NEWLINE)
n->flags |= NODE_LINE;
man->flags &= ~MDOC_NEWLINE;
struct roff_node *n;
struct tbl_span *span;
- if (man->macroset == MACROSET_MAN)
+ if (man->meta.macroset == MACROSET_MAN)
man_breakscope(man, ROFF_TS);
while ((span = tbl_span(tbl)) != NULL) {
n = roff_node_alloc(man, line, 0, ROFFT_TBL, TOKEN_NONE);
man->next = ROFF_NEXT_SIBLING;
}
}
- if (man->first == n)
- man->first = NULL;
+ if (man->meta.first == n)
+ man->meta.first = NULL;
}
void
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;
/* --- main functions of the roff parser ---------------------------------- */
/*
- * In the current line, expand escape sequences that tend to get
- * used in numerical expressions and conditional requests.
- * Also check the syntax of the remaining escape sequences.
+ * In the current line, expand escape sequences that produce parsable
+ * input text. Also check the syntax of the remaining escape sequences,
+ * which typically produce output glyphs or change formatter state.
*/
static int
-roff_res(struct roff *r, struct buf *buf, int ln, int pos)
+roff_expand(struct roff *r, struct buf *buf, int ln, int pos, char newesc)
{
struct mctx *ctx; /* current macro call context */
char ubuf[24]; /* buffer to print the number */
done = 0;
start = buf->buf + pos;
for (stesc = buf->buf + pos; *stesc != '\0'; stesc++) {
- if (stesc[0] != r->escape || stesc[1] == '\0')
+ if (stesc[0] != newesc || stesc[1] == '\0')
continue;
stesc++;
if (*stesc != '"' && *stesc != '#')
* in the syntax tree.
*/
- if (r->format == 0) {
+ if (newesc != ASCII_ESC && r->options & MPARSE_COMMENT) {
while (*ep == ' ' || *ep == '\t')
ep--;
ep[1] = '\0';
expand_count = 0;
while (stesc >= start) {
+ if (*stesc != newesc) {
- /* Search backwards for the next backslash. */
+ /*
+ * If we have a non-standard escape character,
+ * escape literal backslashes because all
+ * processing in subsequent functions uses
+ * the standard escaping rules.
+ */
- if (*stesc != r->escape) {
- if (*stesc == '\\') {
+ if (newesc != ASCII_ESC && *stesc == '\\') {
*stesc = '\0';
buf->sz = mandoc_asprintf(&nbuf, "%s\\e%s",
buf->buf, stesc + 1) + 1;
free(buf->buf);
buf->buf = nbuf;
}
+
+ /* Search backwards for the next escape. */
+
stesc--;
continue;
}
* or to the NUL byte terminating the argument line.
*/
char *
-mandoc_getarg(char **cpp, int ln, int *pos)
+roff_getarg(struct roff *r, char **cpp, int ln, int *pos)
{
- char *start, *cp;
- int quoted, pairs, white;
+ struct buf buf;
+ char *cp, *start;
+ int newesc, pairs, quoted, white;
/* Quoting can only start with a new word. */
start = *cpp;
start++;
}
- pairs = 0;
- white = 0;
+ newesc = pairs = white = 0;
for (cp = start; '\0' != *cp; cp++) {
/*
case 'a':
case 't':
cp[-pairs] = '\t';
- /* FALLTHROUGH */
+ pairs++;
+ cp++;
+ break;
case '\\':
+ newesc = 1;
+ cp[-pairs] = ASCII_ESC;
pairs++;
cp++;
break;
if ('\0' == *cp && (white || ' ' == cp[-1]))
mandoc_msg(MANDOCERR_SPACE_EOL, ln, *pos, NULL);
- return start;
+ start = mandoc_strdup(start);
+ if (newesc == 0)
+ return start;
+
+ buf.buf = start;
+ buf.sz = strlen(start) + 1;
+ buf.next = NULL;
+ if (roff_expand(r, &buf, ln, 0, ASCII_ESC) & ROFF_IGN) {
+ free(buf.buf);
+ buf.buf = mandoc_strdup("");
+ }
+ return buf.buf;
}
}
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 &&
/* Expand some escape sequences. */
- e = roff_res(r, buf, ln, pos);
+ e = roff_expand(r, buf, ln, pos, r->escape);
if ((e & ROFF_MASK) == ROFF_IGN)
return e;
assert(e == ROFF_CONT);
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;
-
}
+/*
+ * Pop all nodes ending at the end of the current input line.
+ * Return the number of loops ended.
+ */
static int
roffnode_cleanscope(struct roff *r)
{
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);
return inloop;
}
+/*
+ * Handle the closing "\}" of a conditional block.
+ * Apart from generating warnings, this only pops nodes.
+ * Return the number of loops ended.
+ */
static int
roff_ccond(struct roff *r, int ln, int ppos)
{
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)
{
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;
}
}
-
- /*
- * 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);
- irc |= t != TOKEN_NONE && (rr || roffs[t].flags & ROFFMAC_STRUCT) ?
- (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs) :
- rr ? ROFF_CONT : ROFF_IGN;
+ *offs = rr;
return irc;
}
+/*
+ * Parse and process a request or macro line in conditional scope.
+ */
static int
-roff_cond_text(ROFF_ARGS)
+roff_cond_sub(ROFF_ARGS)
{
- char *ep;
- int endloop, irc, rr;
+ struct roffnode *bl;
+ int irc, rr;
+ enum roff_tok t;
- irc = ROFF_IGN;
- rr = r->last->rule;
- endloop = tok != ROFF_while ? ROFF_IGN :
- rr ? ROFF_LOOPCONT : ROFF_LOOPEXIT;
- if (roffnode_cleanscope(r))
- irc |= endloop;
+ 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);
- /*
- * If `\}' occurs on a text line with neither preceding
- * nor following characters, drop the line completely.
- */
+ /* For now, let high level macros abort .ce mode. */
- ep = buf->buf + pos;
- if (strcmp(ep, "\\}") == 0)
- rr = 0;
+ 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;
+ }
/*
- * The closing delimiter `\}' rewinds the conditional scope
- * but is otherwise ignored when interpreting the line.
+ * Fully handle known macros when they are structurally
+ * required or when the conditional evaluated to true.
*/
- 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;
+ if (t == ROFF_break) {
+ if (irc & ROFF_LOOPMASK)
+ irc = ROFF_IGN | ROFF_LOOPEXIT;
+ else if (rr) {
+ for (bl = r->last; bl != NULL; bl = bl->parent) {
+ bl->rule = 0;
+ if (bl->tok == ROFF_while)
+ break;
+ }
}
- }
+ } else if (t != TOKEN_NONE &&
+ (rr || roffs[t].flags & ROFFMAC_STRUCT))
+ irc |= (*roffs[t].proc)(r, t, buf, ln, ppos, pos, offs);
+ else
+ irc |= rr ? ROFF_CONT : ROFF_IGN;
+ return irc;
+}
+
+/*
+ * Parse and process a text line in conditional scope.
+ */
+static int
+roff_cond_text(ROFF_ARGS)
+{
+ int irc, rr;
+
+ 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;
roff_getstrn(r, name, sz, &deftype);
istrue = !!deftype;
}
- *pos = cp - v;
+ *pos = (name + sz) - v;
return istrue == wanttrue;
default:
break;
return ROFF_IGN;
namesz = roff_getname(r, &string, ln, pos);
- if (name[namesz] == '\\')
+ switch (name[namesz]) {
+ case '\\':
return ROFF_IGN;
+ case '\t':
+ string = buf->buf + pos + namesz;
+ break;
+ default:
+ break;
+ }
/* Read past the initial double-quote, if any. */
if (*string == '"')
return ROFF_IGN;
keysz = roff_getname(r, &val, ln, pos);
- if (key[keysz] == '\\')
+ if (key[keysz] == '\\' || key[keysz] == '\t')
return ROFF_IGN;
sign = *val;
namesz = roff_getname(r, &cp, ln, (int)(cp - buf->buf));
roff_setstrn(&r->strtab, name, namesz, NULL, 0, 0);
roff_setstrn(&r->rentab, name, namesz, NULL, 0, 0);
- if (name[namesz] == '\\')
+ if (name[namesz] == '\\' || name[namesz] == '\t')
break;
}
return ROFF_IGN;
static int
roff_TE(ROFF_ARGS)
{
+ r->man->flags &= ~ROFF_NONOFILL;
if (r->tbl == NULL) {
mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, ppos, "TE");
return ROFF_IGN;
{
struct roff_node *n;
- if (r->man->macroset == MACROSET_MAN)
+ if (r->man->meta.macroset == MACROSET_MAN)
man_breakscope(r->man, ROFF_EQ);
n = roff_node_alloc(r->man, ln, ppos, ROFFT_EQN, TOKEN_NONE);
if (ln > r->man->last->line)
mandoc_msg(MANDOCERR_BLK_BROKEN, ln, ppos, "TS breaks TS");
tbl_end(r->tbl, 0);
}
+ r->man->flags |= ROFF_NONOFILL;
r->tbl = tbl_alloc(ppos, ln, r->last_tbl);
if (r->last_tbl == NULL)
r->first_tbl = r->tbl;
return ROFF_IGN;
}
+static int
+roff_noarg(ROFF_ARGS)
+{
+ if (r->man->flags & (MAN_BLINE | MAN_ELINE))
+ man_breakscope(r->man, tok);
+ if (tok == ROFF_brp)
+ tok = ROFF_br;
+ roff_elem_alloc(r->man, ln, ppos, tok);
+ if (buf->buf[pos] != '\0')
+ mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
+ "%s %s", roff_name[tok], buf->buf + pos);
+ if (tok == ROFF_nf)
+ r->man->flags |= ROFF_NOFILL;
+ else if (tok == ROFF_fi)
+ r->man->flags &= ~ROFF_NOFILL;
+ r->man->last->flags |= NODE_LINE | NODE_VALID | NODE_ENDED;
+ r->man->next = ROFF_NEXT_SIBLING;
+ return ROFF_IGN;
+}
+
static int
roff_onearg(ROFF_ARGS)
{
return ROFF_IGN;
newsz = roff_getname(r, &oldn, ln, pos);
- if (newn[newsz] == '\\' || *oldn == '\0')
+ if (newn[newsz] == '\\' || newn[newsz] == '\t' || *oldn == '\0')
return ROFF_IGN;
end = oldn;
return ROFF_IGN;
}
+/*
+ * The .break request only makes sense inside conditionals,
+ * and that case is already handled in roff_cond_sub().
+ */
static int
-roff_br(ROFF_ARGS)
+roff_break(ROFF_ARGS)
{
- if (r->man->flags & (MAN_BLINE | MAN_ELINE))
- man_breakscope(r->man, ROFF_br);
- roff_elem_alloc(r->man, ln, ppos, ROFF_br);
- if (buf->buf[pos] != '\0')
- mandoc_msg(MANDOCERR_ARG_SKIP, ln, pos,
- "%s %s", roff_name[tok], buf->buf + pos);
- r->man->last->flags |= NODE_LINE | NODE_VALID | NODE_ENDED;
- r->man->next = ROFF_NEXT_SIBLING;
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ln, pos, "break");
return ROFF_IGN;
}
return ROFF_IGN;
oldsz = roff_getname(r, &newn, ln, pos);
- if (oldn[oldsz] == '\\' || *newn == '\0')
+ if (oldn[oldsz] == '\\' || oldn[oldsz] == '\t' || *newn == '\0')
return ROFF_IGN;
end = newn;
char *arg, *ap, *dst, *src;
size_t sz;
+ /* If the macro is empty, ignore it altogether. */
+
+ if (*r->current_string == '\0')
+ return ROFF_IGN;
+
/* Initialize a new macro stack context. */
if (++r->mstackpos == r->mstacksz) {
ctx->argv = mandoc_reallocarray(ctx->argv,
ctx->argsz, sizeof(*ctx->argv));
}
- arg = mandoc_getarg(&src, ln, &pos);
+ arg = roff_getarg(r, &src, ln, &pos);
sz = 1; /* For the terminating NUL. */
for (ap = arg; *ap != '\0'; ap++)
sz += *ap == '"' ? 4 : 1;
*dst++ = *ap;
}
*dst = '\0';
+ free(arg);
}
/* Replace the macro invocation by the macro definition. */
buf->sz = strlen(buf->buf) + 1;
*offs = 0;
- return buf->sz > 1 && buf->buf[buf->sz - 2] == '\n' ?
+ return buf->buf[buf->sz - 2] == '\n' ?
ROFF_REPARSE | ROFF_USERCALL : ROFF_IGN | ROFF_APPEND;
}
return ROFF_CONT;
}
+/*
+ * Measure the length in bytes of the roff identifier at *cpp
+ * and advance the pointer to the next word.
+ */
static size_t
roff_getname(struct roff *r, char **cpp, int ln, int pos)
{
size_t namesz;
name = *cpp;
- if ('\0' == *name)
+ if (*name == '\0')
return 0;
- /* Read until end of name and terminate it with NUL. */
+ /* Advance cp to the byte after the end of the name. */
+
for (cp = name; 1; cp++) {
- if ('\0' == *cp || ' ' == *cp) {
- namesz = cp - name;
+ namesz = cp - name;
+ if (*cp == '\0')
+ break;
+ if (*cp == ' ' || *cp == '\t') {
+ cp++;
break;
}
- if ('\\' != *cp)
+ if (*cp != '\\')
continue;
- namesz = cp - name;
- if ('{' == cp[1] || '}' == cp[1])
+ if (cp[1] == '{' || cp[1] == '}')
break;
- cp++;
- if ('\\' == *cp)
+ if (*++cp == '\\')
continue;
mandoc_msg(MANDOCERR_NAMESC, ln, pos,
"%.*s", (int)(cp - name + 1), name);
}
/* Read past spaces. */
- while (' ' == *cp)
+
+ while (*cp == ' ')
cp++;
*cpp = cp;
break;
}
}
- if (r->man->macroset != MACROSET_MAN) {
+ if (r->man->meta.macroset != MACROSET_MAN) {
for (tok = MDOC_Dd; tok < MDOC_MAX; tok++) {
if (strncmp(name, roff_name[tok], len) != 0 ||
roff_name[tok][len] != '\0')
}
}
}
- if (r->man->macroset != MACROSET_MDOC) {
+ if (r->man->meta.macroset != MACROSET_MDOC) {
for (tok = MAN_TH; tok < MAN_MAX; tok++) {
if (strncmp(name, roff_name[tok], len) != 0 ||
roff_name[tok][len] != '\0')
/*
* We bail out on bad escapes.
* No need to warn: we already did so when
- * roff_res() was called.
+ * roff_expand() was called.
*/
sz = (int)(p - pp);
res = mandoc_realloc(res, ssz + sz + 1);