-/* $Id: eqn.c,v 1.72 2017/06/29 16:31:15 schwarze Exp $ */
+/* $Id: eqn.c,v 1.84 2020/01/08 12:16:24 schwarze Exp $ */
/*
* Copyright (c) 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
- * Copyright (c) 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org>
+ * Copyright (c) 2014,2015,2017,2018,2020 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
#include <string.h>
#include <time.h>
-#include "mandoc.h"
#include "mandoc_aux.h"
+#include "mandoc.h"
+#include "roff.h"
+#include "eqn.h"
#include "libmandoc.h"
-#include "libroff.h"
+#include "eqn_parse.h"
#define EQN_NEST_MAX 128 /* maximum nesting of defines */
#define STRNEQ(p1, sz1, p2, sz2) \
MODE_TOK
};
+struct eqn_def {
+ char *key;
+ size_t keysz;
+ char *val;
+ size_t valsz;
+};
+
static struct eqn_box *eqn_box_alloc(struct eqn_node *, struct eqn_box *);
-static void eqn_box_free(struct eqn_box *);
static struct eqn_box *eqn_box_makebinary(struct eqn_node *,
- enum eqn_post, struct eqn_box *);
+ struct eqn_box *);
static void eqn_def(struct eqn_node *);
static struct eqn_def *eqn_def_find(struct eqn_node *);
static void eqn_delim(struct eqn_node *);
static enum eqn_tok eqn_next(struct eqn_node *, enum parse_mode);
-static enum rofferr eqn_parse(struct eqn_node *, struct eqn_box *);
static void eqn_undef(struct eqn_node *);
-enum rofferr
-eqn_read(struct eqn_node **epp, int ln,
- const char *p, int pos, int *offs)
+struct eqn_node *
+eqn_alloc(void)
{
- size_t sz;
- struct eqn_node *ep;
- enum rofferr er;
-
- ep = *epp;
-
- /*
- * If we're the terminating mark, unset our equation status and
- * validate the full equation.
- */
-
- if (0 == strncmp(p, ".EN", 3)) {
- er = eqn_end(epp);
- p += 3;
- while (' ' == *p || '\t' == *p)
- p++;
- if ('\0' == *p)
- return er;
- mandoc_vmsg(MANDOCERR_ARG_SKIP, ep->parse,
- ln, pos, "EN %s", p);
- return er;
- }
-
- /*
- * Build up the full string, replacing all newlines with regular
- * whitespace.
- */
-
- sz = strlen(p + pos) + 1;
- ep->data = mandoc_realloc(ep->data, ep->sz + sz + 1);
-
- /* First invocation: nil terminate the string. */
+ struct eqn_node *ep;
- if (0 == ep->sz)
- *ep->data = '\0';
-
- ep->sz += sz;
- strlcat(ep->data, p + pos, ep->sz + 1);
- strlcat(ep->data, " ", ep->sz + 1);
- return ROFF_IGN;
+ ep = mandoc_calloc(1, sizeof(*ep));
+ ep->gsize = EQN_DEFSIZE;
+ return ep;
}
-struct eqn_node *
-eqn_alloc(int pos, int line, struct mparse *parse)
+void
+eqn_reset(struct eqn_node *ep)
{
- struct eqn_node *p;
-
- p = mandoc_calloc(1, sizeof(struct eqn_node));
+ free(ep->data);
+ ep->data = ep->start = ep->end = NULL;
+ ep->sz = ep->toksz = 0;
+}
- p->parse = parse;
- p->eqn.ln = line;
- p->eqn.pos = pos;
- p->gsize = EQN_DEFSIZE;
+void
+eqn_read(struct eqn_node *ep, const char *p)
+{
+ char *cp;
- return p;
+ if (ep->data == NULL) {
+ ep->sz = strlen(p);
+ ep->data = mandoc_strdup(p);
+ } else {
+ ep->sz = mandoc_asprintf(&cp, "%s %s", ep->data, p);
+ free(ep->data);
+ ep->data = cp;
+ }
+ ep->sz += 1;
}
/*
case '"':
quoted = 1;
break;
+ case ' ':
+ case '\t':
+ case '~':
+ case '^':
+ if (quoted)
+ break;
+ ep->start++;
+ continue;
default:
break;
}
ep->end = strchr(ep->start + 1, *ep->start);
ep->start++; /* Skip opening quote. */
if (ep->end == NULL) {
- mandoc_msg(MANDOCERR_ARG_QUOTE, ep->parse,
- ep->eqn.ln, ep->eqn.pos, NULL);
+ mandoc_msg(MANDOCERR_ARG_QUOTE,
+ ep->node->line, ep->node->pos, NULL);
ep->end = strchr(ep->start, '\0');
}
} else {
if ((def = eqn_def_find(ep)) == NULL)
break;
if (++lim > EQN_NEST_MAX) {
- mandoc_msg(MANDOCERR_ROFFLOOP, ep->parse,
- ep->eqn.ln, ep->eqn.pos, NULL);
+ mandoc_msg(MANDOCERR_ROFFLOOP,
+ ep->node->line, ep->node->pos, NULL);
return EQN_TOK_EOF;
}
return EQN_TOK__MAX;
}
-static void
+void
eqn_box_free(struct eqn_box *bp)
{
+ if (bp == NULL)
+ return;
if (bp->first)
eqn_box_free(bp->first);
free(bp);
}
+struct eqn_box *
+eqn_box_new(void)
+{
+ struct eqn_box *bp;
+
+ bp = mandoc_calloc(1, sizeof(*bp));
+ bp->expectargs = UINT_MAX;
+ return bp;
+}
+
/*
* Allocate a box as the last child of the parent node.
*/
{
struct eqn_box *bp;
- bp = mandoc_calloc(1, sizeof(struct eqn_box));
+ bp = eqn_box_new();
bp->parent = parent;
bp->parent->args++;
- bp->expectargs = UINT_MAX;
bp->font = bp->parent->font;
bp->size = ep->gsize;
* The new EQN_SUBEXPR will have a two-child limit.
*/
static struct eqn_box *
-eqn_box_makebinary(struct eqn_node *ep,
- enum eqn_post pos, struct eqn_box *parent)
+eqn_box_makebinary(struct eqn_node *ep, struct eqn_box *parent)
{
struct eqn_box *b, *newb;
parent->last = b->prev;
b->prev = NULL;
newb = eqn_box_alloc(ep, parent);
- newb->pos = pos;
newb->type = EQN_SUBEXPR;
newb->expectargs = 2;
newb->args = 1;
eqn_delim(struct eqn_node *ep)
{
if (ep->end[0] == '\0' || ep->end[1] == '\0') {
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, "delim");
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "delim");
if (ep->end[0] != '\0')
ep->end++;
} else if (strncmp(ep->end, "off", 3) == 0) {
struct eqn_def *def;
if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, "undef");
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "undef");
return;
}
if ((def = eqn_def_find(ep)) == NULL)
int i;
if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF) {
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, "define");
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "define");
return;
}
}
if (eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF) {
- mandoc_vmsg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, "define %s", def->key);
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "define %s", def->key);
free(def->key);
free(def->val);
def->key = def->val = NULL;
def->valsz = ep->toksz;
}
-/*
- * Recursively parse an eqn(7) expression.
- */
-static enum rofferr
-eqn_parse(struct eqn_node *ep, struct eqn_box *parent)
+void
+eqn_parse(struct eqn_node *ep)
{
- struct eqn_box *cur, *nbox, *split;
+ struct eqn_box *cur, *nbox, *parent, *split;
const char *cp, *cpn;
char *p;
enum eqn_tok tok;
- enum eqn_post pos;
enum { CCL_LET, CCL_DIG, CCL_PUN } ccl, ccln;
int size;
+ parent = ep->node->eqn;
assert(parent != NULL);
/*
*/
if (ep->data == NULL)
- return ROFF_IGN;
+ return;
- ep->start = ep->end = ep->data + strspn(ep->data, " ^~");
+ ep->start = ep->end = ep->data;
next_tok:
tok = eqn_next(ep, MODE_TOK);
case EQN_TOK_TDEFINE:
if (eqn_next(ep, MODE_NOSUB) == EQN_TOK_EOF ||
eqn_next(ep, MODE_QUOTED) == EQN_TOK_EOF)
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, "tdefine");
+ mandoc_msg(MANDOCERR_REQ_EMPTY,
+ ep->node->line, ep->node->pos, "tdefine");
break;
case EQN_TOK_DELIM:
eqn_delim(ep);
break;
case EQN_TOK_GFONT:
if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
break;
case EQN_TOK_MARK:
case EQN_TOK_LINEUP:
case EQN_TOK_DOT:
case EQN_TOK_DOTDOT:
if (parent->last == NULL) {
- mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
cur = eqn_box_alloc(ep, parent);
cur->type = EQN_TEXT;
cur->text = mandoc_strdup("");
}
- parent = eqn_box_makebinary(ep, EQNPOS_NONE, parent);
- parent->type = EQN_LISTONE;
+ parent = eqn_box_makebinary(ep, parent);
+ parent->type = EQN_LIST;
parent->expectargs = 1;
parent->font = EQNFONT_ROMAN;
switch (tok) {
parent->bottom = mandoc_strdup("\\[ul]");
break;
case EQN_TOK_BAR:
- parent->top = mandoc_strdup("\\[rl]");
+ parent->top = mandoc_strdup("\\[rn]");
break;
case EQN_TOK_DOT:
parent->top = mandoc_strdup("\\[a.]");
case EQN_TOK_DOWN:
case EQN_TOK_UP:
if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF)
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
break;
case EQN_TOK_FAT:
case EQN_TOK_ROMAN:
* exactly one of those.
*/
parent = eqn_box_alloc(ep, parent);
- parent->type = EQN_LISTONE;
+ parent->type = EQN_LIST;
parent->expectargs = 1;
switch (tok) {
case EQN_TOK_FAT:
case EQN_TOK_GSIZE:
/* Accept two values: integral size and a single. */
if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
break;
}
size = mandoc_strntoi(ep->start, ep->toksz, 10);
if (-1 == size) {
- mandoc_msg(MANDOCERR_IT_NONUM, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_IT_NONUM, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
break;
}
if (EQN_TOK_GSIZE == tok) {
ep->gsize = size;
break;
}
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
parent = eqn_box_alloc(ep, parent);
- parent->type = EQN_LISTONE;
+ parent->type = EQN_LIST;
parent->expectargs = 1;
parent->size = size;
break;
* and keep on reading.
*/
if (parent->last == NULL) {
- mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
cur = eqn_box_alloc(ep, parent);
cur->type = EQN_TEXT;
cur->text = mandoc_strdup("");
}
- /* Handle the "subsup" and "fromto" positions. */
- if (EQN_TOK_SUP == tok && parent->pos == EQNPOS_SUB) {
+ while (parent->expectargs == 1 && parent->args == 1)
+ parent = parent->parent;
+ if (tok == EQN_TOK_FROM || tok == EQN_TOK_TO) {
+ for (cur = parent; cur != NULL; cur = cur->parent)
+ if (cur->pos == EQNPOS_SUB ||
+ cur->pos == EQNPOS_SUP ||
+ cur->pos == EQNPOS_SUBSUP ||
+ cur->pos == EQNPOS_SQRT ||
+ cur->pos == EQNPOS_OVER)
+ break;
+ if (cur != NULL)
+ parent = cur->parent;
+ }
+ if (tok == EQN_TOK_SUP && parent->pos == EQNPOS_SUB) {
parent->expectargs = 3;
parent->pos = EQNPOS_SUBSUP;
break;
}
- if (EQN_TOK_TO == tok && parent->pos == EQNPOS_FROM) {
+ if (tok == EQN_TOK_TO && parent->pos == EQNPOS_FROM) {
parent->expectargs = 3;
parent->pos = EQNPOS_FROMTO;
break;
}
+ parent = eqn_box_makebinary(ep, parent);
switch (tok) {
case EQN_TOK_FROM:
- pos = EQNPOS_FROM;
+ parent->pos = EQNPOS_FROM;
break;
case EQN_TOK_TO:
- pos = EQNPOS_TO;
+ parent->pos = EQNPOS_TO;
break;
case EQN_TOK_SUP:
- pos = EQNPOS_SUP;
+ parent->pos = EQNPOS_SUP;
break;
case EQN_TOK_SUB:
- pos = EQNPOS_SUB;
+ parent->pos = EQNPOS_SUB;
break;
default:
abort();
}
- parent = eqn_box_makebinary(ep, pos, parent);
break;
case EQN_TOK_SQRT:
while (parent->args == parent->expectargs)
* rebalance and continue reading.
*/
if (parent->last == NULL) {
- mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_EQN_NOBOX, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
cur = eqn_box_alloc(ep, parent);
cur->type = EQN_TEXT;
cur->text = mandoc_strdup("");
}
+ while (parent->args == parent->expectargs)
+ parent = parent->parent;
while (EQN_SUBEXPR == parent->type)
parent = parent->parent;
- parent = eqn_box_makebinary(ep, EQNPOS_OVER, parent);
+ parent = eqn_box_makebinary(ep, parent);
+ parent->pos = EQNPOS_OVER;
break;
case EQN_TOK_RIGHT:
case EQN_TOK_BRACE_CLOSE:
*/
for (cur = parent; cur != NULL; cur = cur->parent)
if (cur->type == EQN_LIST &&
+ cur->expectargs > 1 &&
(tok == EQN_TOK_BRACE_CLOSE ||
cur->left != NULL))
break;
if (cur == NULL) {
- mandoc_msg(MANDOCERR_BLK_NOTOPEN, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_BLK_NOTOPEN, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
break;
}
parent = cur;
if (EQN_TOK_RIGHT == tok) {
if (eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
mandoc_msg(MANDOCERR_REQ_EMPTY,
- ep->parse, ep->eqn.ln,
- ep->eqn.pos, eqn_toks[tok]);
+ ep->node->line, ep->node->pos,
+ "%s", eqn_toks[tok]);
break;
}
/* Handling depends on right/left. */
parent->type == EQN_MATRIX))
parent = parent->parent;
/* Close out any "singleton" lists. */
- while (parent->type == EQN_LISTONE &&
- parent->args == parent->expectargs)
+ while (parent->type == EQN_LIST &&
+ parent->expectargs == 1 &&
+ parent->args == 1)
parent = parent->parent;
break;
case EQN_TOK_BRACE_OPEN:
parent = parent->parent;
if (EQN_TOK_LEFT == tok &&
eqn_next(ep, MODE_SUB) == EQN_TOK_EOF) {
- mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_REQ_EMPTY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
break;
}
parent = eqn_box_alloc(ep, parent);
if (cur->type == EQN_PILE)
break;
if (cur == NULL) {
- mandoc_msg(MANDOCERR_IT_STRAY, ep->parse,
- ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]);
+ mandoc_msg(MANDOCERR_IT_STRAY, ep->node->line,
+ ep->node->pos, "%s", eqn_toks[tok]);
break;
}
parent = eqn_box_alloc(ep, cur);
parent->expectargs = 1;
break;
case EQN_TOK_EOF:
- /*
- * End of file!
- * TODO: make sure we're not in an open subexpression.
- */
- return ROFF_EQN;
+ return;
case EQN_TOK__MAX:
case EQN_TOK_FUNC:
case EQN_TOK_QUOTED:
/* No boundary after last character. */
if (*cpn == '\0')
break;
- if (ccln == ccl)
+ if (ccln == ccl && *cp != ',' && *cpn != ',')
continue;
/* Boundary found, split the text. */
if (parent->args == parent->expectargs) {
parent = split->parent;
break;
}
- /*
- * Post-process list status.
- */
- while (parent->type == EQN_LISTONE &&
- parent->args == parent->expectargs)
- parent = parent->parent;
break;
default:
abort();
goto next_tok;
}
-enum rofferr
-eqn_end(struct eqn_node **epp)
-{
- struct eqn_node *ep;
-
- ep = *epp;
- *epp = NULL;
-
- ep->eqn.root = mandoc_calloc(1, sizeof(struct eqn_box));
- ep->eqn.root->expectargs = UINT_MAX;
- return eqn_parse(ep, ep->eqn.root);
-}
-
void
eqn_free(struct eqn_node *p)
{
int i;
- eqn_box_free(p->eqn.root);
+ if (p == NULL)
+ return;
for (i = 0; i < (int)p->defsz; i++) {
free(p->defs[i].key);