-/* $Id: roff.c,v 1.350 2018/12/14 05:18:03 schwarze Exp $ */
+/* $Id: roff.c,v 1.378 2021/08/10 12:55:04 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
};
struct roff {
- struct mparse *parse; /* parse point */
struct roff_man *man; /* mdoc or man parser */
struct roffnode *last; /* leaf of stack */
struct mctx *mstack; /* stack of macro contexts */
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++)
}
struct roff *
-roff_alloc(struct mparse *parse, int options)
+roff_alloc(int options)
{
struct roff *r;
r = mandoc_calloc(1, sizeof(struct roff));
- r->parse = parse;
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);
}
struct roff_man *
-roff_man_alloc(struct roff *roff, struct mparse *parse,
- const char *os_s, int quick)
+roff_man_alloc(struct roff *roff, const char *os_s, int quick)
{
struct roff_man *man;
man = mandoc_calloc(1, sizeof(*man));
- man->parse = parse;
man->roff = roff;
man->os_s = os_s;
man->quick = quick;
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 */
struct roff_node *n; /* used for header comments */
const char *start; /* start of the string to process */
char *stesc; /* start of an escape sequence ('\\') */
+ const char *esct; /* type of esccape sequence */
char *ep; /* end of comment string */
const char *stnam; /* start of the name, after "[(*" */
const char *cp; /* end of the name, e.g. before ']' */
size_t naml; /* actual length of the escape name */
size_t asz; /* length of the replacement */
size_t rsz; /* length of the rest of the string */
- enum mandoc_esc esc; /* type of the escape sequence */
int inaml; /* length returned from mandoc_escape() */
int expand_count; /* to avoid infinite loops */
int npos; /* position in numeric expression */
int done; /* no more input available */
int deftype; /* type of definition to paste */
int rcsid; /* kind of RCS id seen */
+ enum mandocerr err; /* for escape sequence problems */
char sign; /* increment number register */
char term; /* character terminating the escape */
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;
}
term = '\0';
cp = stesc + 1;
- switch (*cp) {
+ if (*cp == 'E')
+ cp++;
+ esct = cp;
+ switch (*esct) {
case '*':
case '$':
res = NULL;
res = ubuf;
break;
default:
- esc = mandoc_escape(&cp, &stnam, &inaml);
- if (esc == ESCAPE_ERROR ||
- (esc == ESCAPE_SPECIAL &&
- mchars_spec2cp(stnam, inaml) < 0))
- mandoc_msg(MANDOCERR_ESC_BAD,
- ln, (int)(stesc - buf->buf),
+ err = MANDOCERR_OK;
+ switch(mandoc_escape(&cp, &stnam, &inaml)) {
+ case ESCAPE_SPECIAL:
+ if (mchars_spec2cp(stnam, inaml) >= 0)
+ break;
+ /* FALLTHROUGH */
+ case ESCAPE_ERROR:
+ err = MANDOCERR_ESC_BAD;
+ break;
+ case ESCAPE_UNDEF:
+ err = MANDOCERR_ESC_UNDEF;
+ break;
+ case ESCAPE_UNSUPP:
+ err = MANDOCERR_ESC_UNSUPP;
+ break;
+ default:
+ break;
+ }
+ if (err != MANDOCERR_OK)
+ mandoc_msg(err, ln, (int)(stesc - buf->buf),
"%.*s", (int)(cp - stesc), stesc);
stesc--;
continue;
cp++;
break;
}
- if (*cp++ != '\\' || stesc[1] != 'w') {
+ if (*cp++ != '\\' || *esct != 'w') {
naml++;
continue;
}
case ESCAPE_SPECIAL:
case ESCAPE_UNICODE:
case ESCAPE_NUMBERED:
+ case ESCAPE_UNDEF:
case ESCAPE_OVERSTRIKE:
naml++;
break;
* undefined, resume searching for escapes.
*/
- switch (stesc[1]) {
+ switch (*esct) {
case '*':
if (arg_complete) {
deftype = ROFFDEF_USER | ROFFDEF_PRE;
break;
}
ctx = r->mstack + r->mstackpos;
- npos = stesc[2] - '1';
+ npos = esct[1] - '1';
if (npos >= 0 && npos <= 8) {
res = npos < ctx->argc ?
ctx->argv[npos] : "";
break;
}
- if (stesc[2] == '*')
+ if (esct[1] == '*')
quote_args = 0;
- else if (stesc[2] == '@')
+ else if (esct[1] == '@')
quote_args = 1;
else {
mandoc_msg(MANDOCERR_ARG_NONUM, ln,
}
if (res == NULL) {
- if (stesc[1] == '*')
+ if (*esct == '*')
mandoc_msg(MANDOCERR_STR_UNDEF,
ln, (int)(stesc - buf->buf),
"%.*s", (int)naml, stnam);
return ROFF_CONT;
}
+/*
+ * Parse a quoted or unquoted roff-style request or macro argument.
+ * Return a pointer to the parsed argument, which is either the original
+ * pointer or advanced by one byte in case the argument is quoted.
+ * NUL-terminate the argument in place.
+ * Collapse pairs of quotes inside quoted arguments.
+ * Advance the argument pointer to the next argument,
+ * or to the NUL byte terminating the argument line.
+ */
+char *
+roff_getarg(struct roff *r, char **cpp, int ln, int *pos)
+{
+ struct buf buf;
+ char *cp, *start;
+ int newesc, pairs, quoted, white;
+
+ /* Quoting can only start with a new word. */
+ start = *cpp;
+ quoted = 0;
+ if ('"' == *start) {
+ quoted = 1;
+ start++;
+ }
+
+ newesc = pairs = white = 0;
+ for (cp = start; '\0' != *cp; cp++) {
+
+ /*
+ * Move the following text left
+ * after quoted quotes and after "\\" and "\t".
+ */
+ if (pairs)
+ cp[-pairs] = cp[0];
+
+ if ('\\' == cp[0]) {
+ /*
+ * In copy mode, translate double to single
+ * backslashes and backslash-t to literal tabs.
+ */
+ switch (cp[1]) {
+ case 'a':
+ case 't':
+ cp[-pairs] = '\t';
+ pairs++;
+ cp++;
+ break;
+ case '\\':
+ newesc = 1;
+ cp[-pairs] = ASCII_ESC;
+ pairs++;
+ cp++;
+ break;
+ case ' ':
+ /* Skip escaped blanks. */
+ if (0 == quoted)
+ cp++;
+ break;
+ default:
+ break;
+ }
+ } else if (0 == quoted) {
+ if (' ' == cp[0]) {
+ /* Unescaped blanks end unquoted args. */
+ white = 1;
+ break;
+ }
+ } else if ('"' == cp[0]) {
+ if ('"' == cp[1]) {
+ /* Quoted quotes collapse. */
+ pairs++;
+ cp++;
+ } else {
+ /* Unquoted quotes end quoted args. */
+ quoted = 2;
+ break;
+ }
+ }
+ }
+
+ /* Quoted argument without a closing quote. */
+ if (1 == quoted)
+ mandoc_msg(MANDOCERR_ARG_QUOTE, ln, *pos, NULL);
+
+ /* NUL-terminate this argument and move to the next one. */
+ if (pairs)
+ cp[-pairs] = '\0';
+ if ('\0' != *cp) {
+ *cp++ = '\0';
+ while (' ' == *cp)
+ cp++;
+ }
+ *pos += (int)(cp - start) + (quoted ? 1 : 0);
+ *cpp = cp;
+
+ if ('\0' == *cp && (white || ' ' == cp[-1]))
+ mandoc_msg(MANDOCERR_SPACE_EOL, ln, *pos, NULL);
+
+ 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;
+}
+
+
/*
* Process text streams.
*/
}
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)
assert(r->eqn == NULL);
if (r->last_eqn == NULL)
- r->last_eqn = eqn_alloc(r->parse);
+ r->last_eqn = eqn_alloc();
else
eqn_reset(r->last_eqn);
r->eqn = r->last_eqn;
mandoc_msg(MANDOCERR_BLK_BROKEN, ln, ppos, "TS breaks TS");
tbl_end(r->tbl, 0);
}
- r->tbl = tbl_alloc(ppos, ln, r->parse, r->last_tbl);
+ r->man->flags |= ROFF_NONOFILL;
+ r->tbl = tbl_alloc(ppos, ln, r->last_tbl);
if (r->last_tbl == NULL)
r->first_tbl = r->tbl;
r->last_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;
}
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;
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(r->parse, &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);