static int eqn_do_define(struct eqn_node *ep) { const char *start; size_t sz; struct eqn_def *def; int i; if (NULL == (start = eqn_nextrawtok(ep, &sz))) { EQN_MSG(MANDOCERR_EQNEOF, ep); return(0); } /* * Search for a key that already exists. * Create a new key if none is found. */ if (NULL == (def = eqn_def_find(ep, start, sz))) { /* Find holes in string array. */ for (i = 0; i < (int)ep->defsz; i++) if (0 == ep->defs[i].keysz) break; if (i == (int)ep->defsz) { ep->defsz++; ep->defs = mandoc_realloc (ep->defs, ep->defsz * sizeof(struct eqn_def)); ep->defs[i].key = ep->defs[i].val = NULL; } ep->defs[i].keysz = sz; ep->defs[i].key = mandoc_realloc (ep->defs[i].key, sz + 1); memcpy(ep->defs[i].key, start, sz); ep->defs[i].key[(int)sz] = '\0'; def = &ep->defs[i]; } start = eqn_next(ep, ep->data[(int)ep->cur], &sz, 0); if (NULL == start) { EQN_MSG(MANDOCERR_EQNEOF, ep); return(0); } def->valsz = sz; def->val = mandoc_realloc(def->val, sz + 1); memcpy(def->val, start, sz); def->val[(int)sz] = '\0'; return(1); }
static int eqn_do_tdefine(struct eqn_node *ep) { if (NULL == eqn_nextrawtok(ep, NULL)) EQN_MSG(MANDOCERR_EQNEOF, ep); else if (NULL == eqn_next(ep, ep->data[(int)ep->cur], NULL, 0)) EQN_MSG(MANDOCERR_EQNEOF, ep); else return(1); return(0); }
static void eqn_def(struct eqn_node *ep) { const char *start; size_t sz; struct eqn_def *def; int i; if ((start = eqn_nextrawtok(ep, &sz)) == NULL) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, "define"); return; } /* * Search for a key that already exists. * Create a new key if none is found. */ if (NULL == (def = eqn_def_find(ep, start, sz))) { /* Find holes in string array. */ for (i = 0; i < (int)ep->defsz; i++) if (0 == ep->defs[i].keysz) break; if (i == (int)ep->defsz) { ep->defsz++; ep->defs = mandoc_reallocarray(ep->defs, ep->defsz, sizeof(struct eqn_def)); ep->defs[i].key = ep->defs[i].val = NULL; } def = ep->defs + i; free(def->key); def->key = mandoc_strndup(start, sz); def->keysz = sz; } start = eqn_next(ep, ep->data[(int)ep->cur], &sz, 0); if (start == NULL) { mandoc_vmsg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, "define %s", def->key); free(def->key); free(def->val); def->key = def->val = NULL; def->keysz = def->valsz = 0; return; } free(def->val); def->val = mandoc_strndup(start, sz); def->valsz = sz; }
/* * Recursively parse an eqn(7) expression. */ static enum rofferr eqn_parse(struct eqn_node *ep, struct eqn_box *parent) { char sym[64]; struct eqn_box *cur; const char *start; char *p; size_t i, sz; enum eqn_tok tok, subtok; enum eqn_post pos; int size; assert(parent != NULL); /* * Empty equation. * Do not add it to the high-level syntax tree. */ if (ep->data == NULL) return(ROFF_IGN); next_tok: tok = eqn_tok_parse(ep, &p); this_tok: switch (tok) { case (EQN_TOK_UNDEF): eqn_undef(ep); break; case (EQN_TOK_NDEFINE): case (EQN_TOK_DEFINE): eqn_def(ep); break; case (EQN_TOK_TDEFINE): if (eqn_nextrawtok(ep, NULL) == NULL || eqn_next(ep, ep->data[(int)ep->cur], NULL, 0) == NULL) mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, "tdefine"); break; case (EQN_TOK_DELIM): eqn_delim(ep); break; case (EQN_TOK_GFONT): if (eqn_nextrawtok(ep, NULL) == NULL) mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); break; case (EQN_TOK_MARK): case (EQN_TOK_LINEUP): /* Ignore these. */ break; case (EQN_TOK_DYAD): case (EQN_TOK_VEC): case (EQN_TOK_UNDER): case (EQN_TOK_BAR): case (EQN_TOK_TILDE): case (EQN_TOK_HAT): 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]); 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->expectargs = 1; switch (tok) { case (EQN_TOK_DOTDOT): strlcpy(sym, "\\[ad]", sizeof(sym)); break; case (EQN_TOK_VEC): strlcpy(sym, "\\[->]", sizeof(sym)); break; case (EQN_TOK_DYAD): strlcpy(sym, "\\[<>]", sizeof(sym)); break; case (EQN_TOK_TILDE): strlcpy(sym, "\\[a~]", sizeof(sym)); break; case (EQN_TOK_UNDER): strlcpy(sym, "\\[ul]", sizeof(sym)); break; case (EQN_TOK_BAR): strlcpy(sym, "\\[rl]", sizeof(sym)); break; case (EQN_TOK_DOT): strlcpy(sym, "\\[a.]", sizeof(sym)); break; case (EQN_TOK_HAT): strlcpy(sym, "\\[ha]", sizeof(sym)); break; default: abort(); } switch (tok) { case (EQN_TOK_DOTDOT): case (EQN_TOK_VEC): case (EQN_TOK_DYAD): case (EQN_TOK_TILDE): case (EQN_TOK_BAR): case (EQN_TOK_DOT): case (EQN_TOK_HAT): parent->top = mandoc_strdup(sym); break; case (EQN_TOK_UNDER): parent->bottom = mandoc_strdup(sym); break; default: abort(); } parent = parent->parent; break; case (EQN_TOK_FWD): case (EQN_TOK_BACK): case (EQN_TOK_DOWN): case (EQN_TOK_UP): subtok = eqn_tok_parse(ep, NULL); if (subtok != EQN_TOK__MAX) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); tok = subtok; goto this_tok; } break; case (EQN_TOK_FAT): case (EQN_TOK_ROMAN): case (EQN_TOK_ITALIC): case (EQN_TOK_BOLD): while (parent->args == parent->expectargs) parent = parent->parent; /* * These values apply to the next word or sequence of * words; thus, we mark that we'll have a child with * exactly one of those. */ parent = eqn_box_alloc(ep, parent); parent->type = EQN_LISTONE; parent->expectargs = 1; switch (tok) { case (EQN_TOK_FAT): parent->font = EQNFONT_FAT; break; case (EQN_TOK_ROMAN): parent->font = EQNFONT_ROMAN; break; case (EQN_TOK_ITALIC): parent->font = EQNFONT_ITALIC; break; case (EQN_TOK_BOLD): parent->font = EQNFONT_BOLD; break; default: abort(); } break; case (EQN_TOK_SIZE): case (EQN_TOK_GSIZE): /* Accept two values: integral size and a single. */ if (NULL == (start = eqn_nexttok(ep, &sz))) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); break; } size = mandoc_strntoi(start, sz, 10); if (-1 == size) { mandoc_msg(MANDOCERR_IT_NONUM, ep->parse, ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); break; } if (EQN_TOK_GSIZE == tok) { ep->gsize = size; break; } parent = eqn_box_alloc(ep, parent); parent->type = EQN_LISTONE; parent->expectargs = 1; parent->size = size; break; case (EQN_TOK_FROM): case (EQN_TOK_TO): case (EQN_TOK_SUB): case (EQN_TOK_SUP): /* * We have a left-right-associative expression. * Repivot under a positional node, open a child scope * and keep on reading. */ if (parent->last == NULL) { mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse, ep->eqn.ln, ep->eqn.pos, 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) { parent->expectargs = 3; parent->pos = EQNPOS_SUBSUP; break; } if (EQN_TOK_TO == tok && parent->pos == EQNPOS_FROM) { parent->expectargs = 3; parent->pos = EQNPOS_FROMTO; break; } switch (tok) { case (EQN_TOK_FROM): pos = EQNPOS_FROM; break; case (EQN_TOK_TO): pos = EQNPOS_TO; break; case (EQN_TOK_SUP): pos = EQNPOS_SUP; break; case (EQN_TOK_SUB): pos = EQNPOS_SUB; break; default: abort(); } parent = eqn_box_makebinary(ep, pos, parent); break; case (EQN_TOK_SQRT): while (parent->args == parent->expectargs) parent = parent->parent; /* * Accept a left-right-associative set of arguments just * like sub and sup and friends but without rebalancing * under a pivot. */ parent = eqn_box_alloc(ep, parent); parent->type = EQN_SUBEXPR; parent->pos = EQNPOS_SQRT; parent->expectargs = 1; break; case (EQN_TOK_OVER): /* * We have a right-left-associative fraction. * Close out anything that's currently open, then * rebalance and continue reading. */ if (parent->last == NULL) { mandoc_msg(MANDOCERR_EQN_NOBOX, ep->parse, ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); cur = eqn_box_alloc(ep, parent); cur->type = EQN_TEXT; cur->text = mandoc_strdup(""); } while (EQN_SUBEXPR == parent->type) parent = parent->parent; parent = eqn_box_makebinary(ep, EQNPOS_OVER, parent); break; case (EQN_TOK_RIGHT): case (EQN_TOK_BRACE_CLOSE): /* * Close out the existing brace. * FIXME: this is a shitty sentinel: we should really * have a native EQN_BRACE type or whatnot. */ for (cur = parent; cur != NULL; cur = cur->parent) if (cur->type == EQN_LIST && (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]); break; } parent = cur; if (EQN_TOK_RIGHT == tok) { if (NULL == (start = eqn_nexttok(ep, &sz))) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); break; } /* Handling depends on right/left. */ if (STRNEQ(start, sz, "ceiling", 7)) { strlcpy(sym, "\\[rc]", sizeof(sym)); parent->right = mandoc_strdup(sym); } else if (STRNEQ(start, sz, "floor", 5)) { strlcpy(sym, "\\[rf]", sizeof(sym)); parent->right = mandoc_strdup(sym); } else parent->right = mandoc_strndup(start, sz); } parent = parent->parent; if (EQN_TOK_BRACE_CLOSE == tok && parent && (parent->type == EQN_PILE || parent->type == EQN_MATRIX)) parent = parent->parent; /* Close out any "singleton" lists. */ while (parent && parent->type == EQN_LISTONE && parent->args == parent->expectargs) parent = parent->parent; break; case (EQN_TOK_BRACE_OPEN): case (EQN_TOK_LEFT): /* * If we already have something in the stack and we're * in an expression, then rewind til we're not any more * (just like with the text node). */ while (parent->args == parent->expectargs) parent = parent->parent; if (EQN_TOK_LEFT == tok && (start = eqn_nexttok(ep, &sz)) == NULL) { mandoc_msg(MANDOCERR_REQ_EMPTY, ep->parse, ep->eqn.ln, ep->eqn.pos, eqn_toks[tok]); break; } parent = eqn_box_alloc(ep, parent); parent->type = EQN_LIST; if (EQN_TOK_LEFT == tok) { if (STRNEQ(start, sz, "ceiling", 7)) { strlcpy(sym, "\\[lc]", sizeof(sym)); parent->left = mandoc_strdup(sym); } else if (STRNEQ(start, sz, "floor", 5)) { strlcpy(sym, "\\[lf]", sizeof(sym)); parent->left = mandoc_strdup(sym); } else parent->left = mandoc_strndup(start, sz); } break; case (EQN_TOK_PILE): case (EQN_TOK_LPILE): case (EQN_TOK_RPILE): case (EQN_TOK_CPILE): case (EQN_TOK_CCOL): case (EQN_TOK_LCOL): case (EQN_TOK_RCOL): while (parent->args == parent->expectargs) parent = parent->parent; parent = eqn_box_alloc(ep, parent); parent->type = EQN_PILE; parent->expectargs = 1; break; case (EQN_TOK_ABOVE): for (cur = parent; cur != NULL; cur = cur->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]); break; } parent = eqn_box_alloc(ep, cur); parent->type = EQN_LIST; break; case (EQN_TOK_MATRIX): while (parent->args == parent->expectargs) parent = parent->parent; parent = eqn_box_alloc(ep, parent); parent->type = EQN_MATRIX; parent->expectargs = 1; break; case (EQN_TOK_EOF): /* * End of file! * TODO: make sure we're not in an open subexpression. */ return(ROFF_EQN); default: assert(tok == EQN_TOK__MAX); assert(NULL != p); /* * If we already have something in the stack and we're * in an expression, then rewind til we're not any more. */ while (parent->args == parent->expectargs) parent = parent->parent; cur = eqn_box_alloc(ep, parent); cur->type = EQN_TEXT; for (i = 0; i < EQNSYM__MAX; i++) if (0 == strcmp(eqnsyms[i].str, p)) { (void)snprintf(sym, sizeof(sym), "\\[%s]", eqnsyms[i].sym); cur->text = mandoc_strdup(sym); free(p); break; } if (i == EQNSYM__MAX) cur->text = p; /* * Post-process list status. */ while (parent->type == EQN_LISTONE && parent->args == parent->expectargs) parent = parent->parent; break; } goto next_tok; }
/* * Get next token without replacement. */ static const char * eqn_nextrawtok(struct eqn_node *ep, size_t *sz) { return(eqn_next(ep, '"', sz, 0)); }
/* * Get the next delimited token using the default current quote * character. */ static const char * eqn_nexttok(struct eqn_node *ep, size_t *sz) { return eqn_next(ep, '"', sz, 1); }