void mdoc_macro(MACRO_PROT_ARGS) { assert(tok < MDOC_MAX); if (mdoc->flags & MDOC_PBODY) { if (tok == MDOC_Dt) { mandoc_vmsg(MANDOCERR_DT_LATE, mdoc->parse, line, ppos, "Dt %s", buf + *pos); return; } } else if ( ! (mdoc_macros[tok].flags & MDOC_PROLOGUE)) { if (mdoc->meta.title == NULL) { mandoc_vmsg(MANDOCERR_DT_NOTITLE, mdoc->parse, line, ppos, "%s %s", mdoc_macronames[tok], buf + *pos); mdoc->meta.title = mandoc_strdup("UNTITLED"); } if (NULL == mdoc->meta.vol) mdoc->meta.vol = mandoc_strdup("LOCAL"); mdoc->flags |= MDOC_PBODY; } (*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf); }
static void check_par(CHKARGS) { switch (n->type) { case MAN_BLOCK: if (0 == n->body->nchild) man_node_delete(man, n); break; case MAN_BODY: if (0 == n->nchild) mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos, "%s empty", man_macronames[n->tok]); break; case MAN_HEAD: if (n->nchild) mandoc_vmsg(MANDOCERR_ARG_SKIP, man->parse, n->line, n->pos, "%s %s%s", man_macronames[n->tok], n->child->string, n->nchild > 1 ? " ..." : ""); break; default: break; } }
/* * Close out a generic explicit macro. */ void blk_close(MACRO_PROT_ARGS) { int ntok; const struct roff_node *nn; char *p; int nrew, target; nrew = 1; switch (tok) { case MAN_RE: ntok = MAN_RS; if ( ! man_args(man, line, pos, buf, &p)) break; for (nn = man->last->parent; nn; nn = nn->parent) if (nn->tok == ntok && nn->type == ROFFT_BLOCK) nrew++; target = strtol(p, &p, 10); if (*p != '\0') mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse, line, p - buf, "RE ... %s", p); if (target == 0) target = 1; nrew -= target; if (nrew < 1) { mandoc_vmsg(MANDOCERR_RE_NOTOPEN, man->parse, line, ppos, "RE %d", target); return; } break; case MAN_UE: ntok = MAN_UR; break; default: abort(); } for (nn = man->last->parent; nn; nn = nn->parent) if (nn->tok == ntok && nn->type == ROFFT_BLOCK && ! --nrew) break; if (nn == NULL) { mandoc_msg(MANDOCERR_BLK_NOTOPEN, man->parse, line, ppos, man_macronames[tok]); rew_scope(man, MAN_PP); } else { line = man->last->line; ppos = man->last->pos; ntok = man->last->tok; man_unscope(man, nn); /* Move a trailing paragraph behind the block. */ if (ntok == MAN_LP || ntok == MAN_PP || ntok == MAN_P) { *pos = strlen(buf); blk_imp(man, ntok, line, ppos, pos, buf); } } }
static int post_ft(CHKARGS) { char *cp; int ok; if (0 == n->nchild) return(1); ok = 0; cp = n->child->string; switch (*cp) { case ('1'): /* FALLTHROUGH */ case ('2'): /* FALLTHROUGH */ case ('3'): /* FALLTHROUGH */ case ('4'): /* FALLTHROUGH */ case ('I'): /* FALLTHROUGH */ case ('P'): /* FALLTHROUGH */ case ('R'): if ('\0' == cp[1]) ok = 1; break; case ('B'): if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2])) ok = 1; break; case ('C'): if ('W' == cp[1] && '\0' == cp[2]) ok = 1; break; default: break; } if (0 == ok) { mandoc_vmsg (MANDOCERR_BADFONT, man->parse, n->line, n->pos, "%s", cp); *cp = '\0'; } if (1 < n->nchild) mandoc_vmsg (MANDOCERR_ARGCOUNT, man->parse, n->line, n->pos, "want one child (have %d)", n->nchild); return(1); }
static void cell(struct tbl_node *tbl, struct tbl_row *rp, int ln, const char *p, int *pos) { int i; enum tbl_cellt c; /* Handle leading vertical lines */ while (p[*pos] == ' ' || p[*pos] == '\t' || p[*pos] == '|') { if (p[*pos] == '|') { if (rp->vert < 2) rp->vert++; else mandoc_msg(MANDOCERR_TBLLAYOUT_VERT, tbl->parse, ln, *pos, NULL); } (*pos)++; } again: while (p[*pos] == ' ' || p[*pos] == '\t') (*pos)++; if (p[*pos] == '.' || p[*pos] == '\0') return; /* Parse the column position (`c', `l', `r', ...). */ for (i = 0; i < KEYS_MAX; i++) if (tolower((unsigned char)p[*pos]) == keys[i].name) break; if (i == KEYS_MAX) { mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse, ln, *pos, "%c", p[*pos]); (*pos)++; goto again; } c = keys[i].key; /* Special cases of spanners. */ if (c == TBL_CELL_SPAN) { if (rp->last == NULL) mandoc_msg(MANDOCERR_TBLLAYOUT_SPAN, tbl->parse, ln, *pos, NULL); else if (rp->last->pos == TBL_CELL_HORIZ || rp->last->pos == TBL_CELL_DHORIZ) c = rp->last->pos; } else if (c == TBL_CELL_DOWN && rp == tbl->first_row) mandoc_msg(MANDOCERR_TBLLAYOUT_DOWN, tbl->parse, ln, *pos, NULL); (*pos)++; /* Allocate cell then parse its modifiers. */ mods(tbl, cell_alloc(tbl, rp, c), ln, p, pos); }
/* * If there is an open sub-block of the target requiring * explicit close-out, postpone closing out the target until * the rew_pending() call closing out the sub-block. */ static int find_pending(struct roff_man *mdoc, int tok, int line, int ppos, struct roff_node *target) { struct roff_node *n; int irc; irc = 0; for (n = mdoc->last; n != NULL && n != target; n = n->parent) { if (n->flags & MDOC_ENDED) { if ( ! (n->flags & MDOC_VALID)) n->flags |= MDOC_BROKEN; continue; } if (n->type == ROFFT_BLOCK && mdoc_macros[n->tok].flags & MDOC_EXPLICIT) { irc = 1; n->flags = MDOC_BROKEN; if (target->type == ROFFT_HEAD) target->flags = MDOC_ENDED; else if ( ! (target->flags & MDOC_ENDED)) { mandoc_vmsg(MANDOCERR_BLK_NEST, mdoc->parse, line, ppos, "%s breaks %s", mdoc_macronames[tok], mdoc_macronames[n->tok]); mdoc_endbody_alloc(mdoc, line, ppos, tok, target, ENDBODY_NOSPACE); } } } return irc; }
static void post_vs(CHKARGS) { if (NULL != n->prev) return; switch (n->parent->tok) { case MAN_SH: /* FALLTHROUGH */ case MAN_SS: mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos, "%s after %s", man_macronames[n->tok], man_macronames[n->parent->tok]); /* FALLTHROUGH */ case MAN_MAX: /* * Don't warn about this because it occurs in pod2man * and would cause considerable (unfixable) warnage. */ man_node_delete(man, n); break; default: break; } }
void man_unscope(struct roff_man *man, const struct roff_node *to) { struct roff_node *n; to = to->parent; n = man->last; while (n != to) { /* Reached the end of the document? */ if (to == NULL && ! (n->flags & MAN_VALID)) { if (man->flags & (MAN_BLINE | MAN_ELINE) && man_macros[n->tok].flags & MAN_SCOPED) { mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line, n->pos, "EOF breaks %s", man_macronames[n->tok]); if (man->flags & MAN_ELINE) man->flags &= ~MAN_ELINE; else { assert(n->type == ROFFT_HEAD); n = n->parent; man->flags &= ~MAN_BLINE; } man->last = n; n = n->parent; roff_node_delete(man, man->last); continue; } if (n->type == ROFFT_BLOCK && man_macros[n->tok].fp == blk_exp) mandoc_msg(MANDOCERR_BLK_NOEND, man->parse, n->line, n->pos, man_macronames[n->tok]); } /* * We might delete the man->last node * in the post-validation phase. * Save a pointer to the parent such that * we know where to continue the iteration. */ man->last = n; n = n->parent; man->last->flags |= MAN_VALID; } /* * If we ended up at the parent of the node we were * supposed to rewind to, that means the target node * got deleted, so add the next node we parse as a child * of the parent instead of as a sibling of the target. */ man->next = (man->last == to) ? ROFF_NEXT_CHILD : ROFF_NEXT_SIBLING; }
static int rew_sub(enum mdoc_type t, struct mdoc *mdoc, enum mdoct tok, int line, int ppos) { struct mdoc_node *n; n = mdoc->last; while (n) { switch (rew_dohalt(tok, t, n)) { case (REWIND_NONE): return(1); case (REWIND_THIS): n->lastline = line - (MDOC_NEWLINE & mdoc->flags && ! (MDOC_EXPLICIT & mdoc_macros[tok].flags)); break; case (REWIND_FORCE): mandoc_vmsg(MANDOCERR_SCOPEBROKEN, mdoc->parse, line, ppos, "%s breaks %s", mdoc_macronames[tok], mdoc_macronames[n->tok]); /* FALLTHROUGH */ case (REWIND_MORE): n->lastline = line - (MDOC_NEWLINE & mdoc->flags ? 1 : 0); n = n->parent; continue; case (REWIND_LATER): if (make_pending(n, tok, mdoc, line, ppos) || MDOC_BLOCK != t) return(1); /* FALLTHROUGH */ case (REWIND_ERROR): mdoc_pmsg(mdoc, line, ppos, MANDOCERR_NOSCOPE); return(1); } break; } assert(n); if ( ! rew_last(mdoc, n)) return(0); /* * The current block extends an enclosing block. * Now that the current block ends, close the enclosing block, too. */ while (NULL != (n = n->pending)) { if ( ! rew_last(mdoc, n)) return(0); if (MDOC_HEAD == n->type && ! mdoc_body_alloc(mdoc, n->line, n->pos, n->tok)) return(0); } return(1); }
static void post_UR(CHKARGS) { if (n->type == MAN_HEAD && n->child == NULL) mandoc_vmsg(MANDOCERR_UR_NOHEAD, man->parse, n->line, n->pos, "UR"); check_part(man, n); }
static void post_OP(CHKARGS) { if (n->nchild == 0) mandoc_msg(MANDOCERR_OP_EMPTY, man->parse, n->line, n->pos, "OP"); else if (n->nchild > 2) { n = n->child->next->next; mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse, n->line, n->pos, "OP ... %s", n->string); } }
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; }
static void post_ft(CHKARGS) { char *cp; int ok; if (0 == n->nchild) return; ok = 0; cp = n->child->string; switch (*cp) { case '1': /* FALLTHROUGH */ case '2': /* FALLTHROUGH */ case '3': /* FALLTHROUGH */ case '4': /* FALLTHROUGH */ case 'I': /* FALLTHROUGH */ case 'P': /* FALLTHROUGH */ case 'R': if ('\0' == cp[1]) ok = 1; break; case 'B': if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2])) ok = 1; break; case 'C': if ('W' == cp[1] && '\0' == cp[2]) ok = 1; break; default: break; } if (0 == ok) { mandoc_vmsg(MANDOCERR_FT_BAD, man->parse, n->line, n->pos, "ft %s", cp); *cp = '\0'; } }
enum rofferr eqn_read(struct eqn_node **epp, int ln, const char *p, int pos, int *offs) { 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. */ 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); }
static void post_IP(CHKARGS) { switch (n->type) { case MAN_BLOCK: if (0 == n->head->nchild && 0 == n->body->nchild) man_node_delete(man, n); break; case MAN_BODY: if (0 == n->parent->head->nchild && 0 == n->nchild) mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos, "%s empty", man_macronames[n->tok]); break; default: break; } }
void blk_exp(MACRO_PROT_ARGS) { struct roff_node *head; char *p; int la; rew_scope(man, tok); roff_block_alloc(man, line, ppos, tok); head = roff_head_alloc(man, line, ppos, tok); la = *pos; if (man_args(man, line, pos, buf, &p)) roff_word_alloc(man, line, la, p); if (buf[*pos] != '\0') mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse, line, *pos, "%s ... %s", roff_name[tok], buf + *pos); man_unscope(man, head); roff_body_alloc(man, line, ppos, tok); }
static void blk_full(MACRO_PROT_ARGS) { int la, nl, parsed; struct mdoc_arg *arg; struct roff_node *blk; /* Our own or a broken block. */ struct roff_node *head; /* Our own head. */ struct roff_node *body; /* Our own body. */ struct roff_node *n; enum margserr ac, lac; char *p; nl = MDOC_NEWLINE & mdoc->flags; if (buf[*pos] == '\0' && (tok == MDOC_Sh || tok == MDOC_Ss)) { mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse, line, ppos, mdoc_macronames[tok]); return; } if ( ! (mdoc_macros[tok].flags & MDOC_EXPLICIT)) { /* Here, tok is one of Sh Ss Nm Nd It. */ blk = NULL; for (n = mdoc->last; n != NULL; n = n->parent) { if (n->flags & MDOC_ENDED) { if ( ! (n->flags & MDOC_VALID)) n->flags |= MDOC_BROKEN; continue; } if (n->type != ROFFT_BLOCK) continue; if (tok == MDOC_It && n->tok == MDOC_Bl) { if (blk != NULL) { mandoc_vmsg(MANDOCERR_BLK_BROKEN, mdoc->parse, line, ppos, "It breaks %s", mdoc_macronames[blk->tok]); rew_pending(mdoc, blk); } break; } if (mdoc_macros[n->tok].flags & MDOC_EXPLICIT) { switch (tok) { case MDOC_Sh: case MDOC_Ss: mandoc_vmsg(MANDOCERR_BLK_BROKEN, mdoc->parse, line, ppos, "%s breaks %s", mdoc_macronames[tok], mdoc_macronames[n->tok]); rew_pending(mdoc, n); n = mdoc->last; continue; case MDOC_It: /* Delay in case it's astray. */ blk = n; continue; default: break; } break; } /* Here, n is one of Sh Ss Nm Nd It. */ if (tok != MDOC_Sh && (n->tok == MDOC_Sh || (tok != MDOC_Ss && (n->tok == MDOC_Ss || (tok != MDOC_It && n->tok == MDOC_It))))) break; /* Item breaking an explicit block. */ if (blk != NULL) { mandoc_vmsg(MANDOCERR_BLK_BROKEN, mdoc->parse, line, ppos, "It breaks %s", mdoc_macronames[blk->tok]); rew_pending(mdoc, blk); blk = NULL; } /* Close out prior implicit scopes. */ rew_last(mdoc, n); } /* Skip items outside lists. */ if (tok == MDOC_It && (n == NULL || n->tok != MDOC_Bl)) { mandoc_vmsg(MANDOCERR_IT_STRAY, mdoc->parse, line, ppos, "It %s", buf + *pos); roff_elem_alloc(mdoc, line, ppos, MDOC_br); rew_elem(mdoc, MDOC_br); return; } } /* * This routine accommodates implicitly- and explicitly-scoped * macro openings. Implicit ones first close out prior scope * (seen above). Delay opening the head until necessary to * allow leading punctuation to print. Special consideration * for `It -column', which has phrase-part syntax instead of * regular child nodes. */ mdoc_argv(mdoc, line, tok, &arg, pos, buf); blk = mdoc_block_alloc(mdoc, line, ppos, tok, arg); head = body = NULL; /* * Exception: Heads of `It' macros in `-diag' lists are not * parsed, even though `It' macros in general are parsed. */ parsed = tok != MDOC_It || mdoc->last->parent->tok != MDOC_Bl || mdoc->last->parent->norm->Bl.type != LIST_diag; /* * The `Nd' macro has all arguments in its body: it's a hybrid * of block partial-explicit and full-implicit. Stupid. */ if (tok == MDOC_Nd) { head = roff_head_alloc(mdoc, line, ppos, tok); rew_last(mdoc, head); body = roff_body_alloc(mdoc, line, ppos, tok); } if (tok == MDOC_Bk) mdoc->flags |= MDOC_KEEP; ac = ARGS_EOLN; for (;;) { /* * If we are right after a tab character, * do not parse the first word for macros. */ if (mdoc->flags & MDOC_PHRASEQN) { mdoc->flags &= ~MDOC_PHRASEQN; mdoc->flags |= MDOC_PHRASEQF; } la = *pos; lac = ac; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN) { if (lac != ARGS_PHRASE || ! (mdoc->flags & MDOC_PHRASEQF)) break; /* * This line ends in a tab; start the next * column now, with a leading blank. */ if (body != NULL) rew_last(mdoc, body); body = roff_body_alloc(mdoc, line, ppos, tok); roff_word_alloc(mdoc, line, ppos, "\\&"); break; } if (tok == MDOC_Bd || tok == MDOC_Bk) { mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, line, la, "%s ... %s", mdoc_macronames[tok], buf + la); break; } if (tok == MDOC_Rs) { mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, line, la, "Rs %s", buf + la); break; } if (ac == ARGS_PUNCT) break; /* * Emit leading punctuation (i.e., punctuation before * the ROFFT_HEAD) for non-phrase types. */ if (head == NULL && ac != ARGS_PHRASE && ac != ARGS_QWORD && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); continue; } /* Open a head if one hasn't been opened. */ if (head == NULL) head = roff_head_alloc(mdoc, line, ppos, tok); if (ac == ARGS_PHRASE) { /* * If we haven't opened a body yet, rewind the * head; if we have, rewind that instead. */ rew_last(mdoc, body == NULL ? head : body); body = roff_body_alloc(mdoc, line, ppos, tok); /* Process to the tab or to the end of the line. */ mdoc->flags |= MDOC_PHRASE; parse_rest(mdoc, TOKEN_NONE, line, &la, buf); mdoc->flags &= ~MDOC_PHRASE; /* There may have been `Ta' macros. */ while (body->next != NULL) body = body->next; continue; } if (macro_or_word(mdoc, tok, line, la, pos, buf, parsed)) break; } if (blk->flags & MDOC_VALID) return; if (head == NULL) head = roff_head_alloc(mdoc, line, ppos, tok); if (nl && tok != MDOC_Bd && tok != MDOC_Bl && tok != MDOC_Rs) append_delims(mdoc, line, pos, buf); if (body != NULL) goto out; if (find_pending(mdoc, tok, line, ppos, head)) return; /* Close out scopes to remain in a consistent state. */ rew_last(mdoc, head); body = roff_body_alloc(mdoc, line, ppos, tok); out: if (mdoc->flags & MDOC_FREECOL) { rew_last(mdoc, body); rew_last(mdoc, blk); mdoc->flags &= ~MDOC_FREECOL; } }
/* * Close out block partial/full explicit. */ static void blk_exp_close(MACRO_PROT_ARGS) { struct roff_node *body; /* Our own body. */ struct roff_node *endbody; /* Our own end marker. */ struct roff_node *itblk; /* An It block starting later. */ struct roff_node *later; /* A sub-block starting later. */ struct roff_node *n; /* Search back to our block. */ struct roff_node *target; /* For find_pending(). */ int j, lastarg, maxargs, nl, pending; enum margserr ac; int atok, ntok; char *p; nl = MDOC_NEWLINE & mdoc->flags; switch (tok) { case MDOC_Ec: maxargs = 1; break; case MDOC_Ek: mdoc->flags &= ~MDOC_KEEP; /* FALLTHROUGH */ default: maxargs = 0; break; } /* * Search backwards for beginnings of blocks, * both of our own and of pending sub-blocks. */ atok = rew_alt(tok); body = endbody = itblk = later = NULL; for (n = mdoc->last; n; n = n->parent) { if (n->flags & MDOC_ENDED) { if ( ! (n->flags & MDOC_VALID)) n->flags |= MDOC_BROKEN; continue; } /* Remember the start of our own body. */ if (n->type == ROFFT_BODY && atok == n->tok) { if (n->end == ENDBODY_NOT) body = n; continue; } if (n->type != ROFFT_BLOCK || n->tok == MDOC_Nm) continue; if (n->tok == MDOC_It) { itblk = n; continue; } if (atok == n->tok) { assert(body); /* * Found the start of our own block. * When there is no pending sub block, * just proceed to closing out. */ if (later == NULL || (tok == MDOC_El && itblk == NULL)) break; /* * When there is a pending sub block, postpone * closing out the current block until the * rew_pending() closing out the sub-block. * Mark the place where the formatting - but not * the scope - of the current block ends. */ mandoc_vmsg(MANDOCERR_BLK_NEST, mdoc->parse, line, ppos, "%s breaks %s", mdoc_macronames[atok], mdoc_macronames[later->tok]); endbody = mdoc_endbody_alloc(mdoc, line, ppos, atok, body, ENDBODY_SPACE); if (tok == MDOC_El) itblk->flags |= MDOC_ENDED | MDOC_BROKEN; /* * If a block closing macro taking arguments * breaks another block, put the arguments * into the end marker. */ if (maxargs) mdoc->next = ROFF_NEXT_CHILD; break; } /* Explicit blocks close out description lines. */ if (n->tok == MDOC_Nd) { rew_last(mdoc, n); continue; } /* Breaking an open sub block. */ n->flags |= MDOC_BROKEN; if (later == NULL) later = n; } if (body == NULL) { mandoc_msg(MANDOCERR_BLK_NOTOPEN, mdoc->parse, line, ppos, mdoc_macronames[tok]); if (later != NULL) later->flags &= ~MDOC_BROKEN; if (maxargs && endbody == NULL) { /* * Stray .Ec without previous .Eo: * Break the output line, keep the arguments. */ roff_elem_alloc(mdoc, line, ppos, MDOC_br); rew_elem(mdoc, MDOC_br); } } else if (endbody == NULL) { rew_last(mdoc, body); if (maxargs) mdoc_tail_alloc(mdoc, line, ppos, atok); } if ( ! (mdoc_macros[tok].flags & MDOC_PARSED)) { if (buf[*pos] != '\0') mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, line, ppos, "%s %s", mdoc_macronames[tok], buf + *pos); if (endbody == NULL && n != NULL) rew_pending(mdoc, n); return; } if (endbody != NULL) n = endbody; ntok = TOKEN_NONE; for (j = 0; ; j++) { lastarg = *pos; if (j == maxargs && n != NULL) rew_last(mdoc, n); ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_PUNCT || ac == ARGS_EOLN) break; ntok = ac == ARGS_QWORD ? TOKEN_NONE : lookup(mdoc, tok, line, lastarg, p); if (ntok == TOKEN_NONE) { dword(mdoc, line, lastarg, p, DELIM_MAX, MDOC_JOIN & mdoc_macros[tok].flags); continue; } if (n != NULL) rew_last(mdoc, n); mdoc->flags &= ~MDOC_NEWLINE; mdoc_macro(mdoc, ntok, line, lastarg, pos, buf); break; } if (n != NULL) { if (ntok != TOKEN_NONE && n->flags & MDOC_BROKEN) { target = n; do target = target->parent; while ( ! (target->flags & MDOC_ENDED)); pending = find_pending(mdoc, ntok, line, ppos, target); } else pending = 0; if ( ! pending) rew_pending(mdoc, n); } if (nl) append_delims(mdoc, line, pos, buf); }
static void blk_full(MACRO_PROT_ARGS) { int la, nl, parsed; struct mdoc_arg *arg; struct mdoc_node *blk; /* Our own or a broken block. */ struct mdoc_node *head; /* Our own head. */ struct mdoc_node *body; /* Our own body. */ struct mdoc_node *n; enum margserr ac, lac; char *p; nl = MDOC_NEWLINE & mdoc->flags; if (buf[*pos] == '\0' && (tok == MDOC_Sh || tok == MDOC_Ss)) { mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse, line, ppos, mdoc_macronames[tok]); return; } if ( ! (mdoc_macros[tok].flags & MDOC_EXPLICIT)) { /* Here, tok is one of Sh Ss Nm Nd It. */ blk = NULL; for (n = mdoc->last; n != NULL; n = n->parent) { if (n->flags & MDOC_ENDED) { if ( ! (n->flags & MDOC_VALID)) n->flags |= MDOC_BROKEN; continue; } if (n->type != MDOC_BLOCK) continue; if (tok == MDOC_It && n->tok == MDOC_Bl) { if (blk != NULL) { mandoc_vmsg(MANDOCERR_BLK_BROKEN, mdoc->parse, line, ppos, "It breaks %s", mdoc_macronames[blk->tok]); rew_pending(mdoc, blk); } break; } if (mdoc_macros[n->tok].flags & MDOC_EXPLICIT) { switch (tok) { case MDOC_Sh: /* FALLTHROUGH */ case MDOC_Ss: mandoc_vmsg(MANDOCERR_BLK_BROKEN, mdoc->parse, line, ppos, "%s breaks %s", mdoc_macronames[tok], mdoc_macronames[n->tok]); rew_pending(mdoc, n); n = mdoc->last; continue; case MDOC_It: /* Delay in case it's astray. */ blk = n; continue; default: break; } break; } /* Here, n is one of Sh Ss Nm Nd It. */ if (tok != MDOC_Sh && (n->tok == MDOC_Sh || (tok != MDOC_Ss && (n->tok == MDOC_Ss || (tok != MDOC_It && n->tok == MDOC_It))))) break; /* Item breaking an explicit block. */ if (blk != NULL) { mandoc_vmsg(MANDOCERR_BLK_BROKEN, mdoc->parse, line, ppos, "It breaks %s", mdoc_macronames[blk->tok]); rew_pending(mdoc, blk); blk = NULL; } /* Close out prior implicit scopes. */ rew_last(mdoc, n); } /* Skip items outside lists. */ if (tok == MDOC_It && (n == NULL || n->tok != MDOC_Bl)) { mandoc_vmsg(MANDOCERR_IT_STRAY, mdoc->parse, line, ppos, "It %s", buf + *pos); mdoc_elem_alloc(mdoc, line, ppos, MDOC_br, NULL); rew_elem(mdoc, MDOC_br); return; } } /* * This routine accommodates implicitly- and explicitly-scoped * macro openings. Implicit ones first close out prior scope * (seen above). Delay opening the head until necessary to * allow leading punctuation to print. Special consideration * for `It -column', which has phrase-part syntax instead of * regular child nodes. */ mdoc_argv(mdoc, line, tok, &arg, pos, buf); blk = mdoc_block_alloc(mdoc, line, ppos, tok, arg); head = body = NULL; /* * Exception: Heads of `It' macros in `-diag' lists are not * parsed, even though `It' macros in general are parsed. */ parsed = tok != MDOC_It || mdoc->last->parent->tok != MDOC_Bl || mdoc->last->parent->norm->Bl.type != LIST_diag; /* * The `Nd' macro has all arguments in its body: it's a hybrid * of block partial-explicit and full-implicit. Stupid. */ if (tok == MDOC_Nd) { head = mdoc_head_alloc(mdoc, line, ppos, tok); rew_last(mdoc, head); body = mdoc_body_alloc(mdoc, line, ppos, tok); } if (tok == MDOC_Bk) mdoc->flags |= MDOC_KEEP; ac = ARGS_PEND; for (;;) { la = *pos; lac = ac; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_EOLN) { if (lac != ARGS_PPHRASE && lac != ARGS_PHRASE) break; /* * This is necessary: if the last token on a * line is a `Ta' or tab, then we'll get * ARGS_EOLN, so we must be smart enough to * reopen our scope if the last parse was a * phrase or partial phrase. */ if (body != NULL) rew_last(mdoc, body); body = mdoc_body_alloc(mdoc, line, ppos, tok); break; } if (tok == MDOC_Bd || tok == MDOC_Bk) { mandoc_vmsg(MANDOCERR_ARG_EXCESS, mdoc->parse, line, la, "%s ... %s", mdoc_macronames[tok], buf + la); break; } if (tok == MDOC_Rs) { mandoc_vmsg(MANDOCERR_ARG_SKIP, mdoc->parse, line, la, "Rs %s", buf + la); break; } if (ac == ARGS_PUNCT) break; /* * Emit leading punctuation (i.e., punctuation before * the MDOC_HEAD) for non-phrase types. */ if (head == NULL && ac != ARGS_PEND && ac != ARGS_PHRASE && ac != ARGS_PPHRASE && ac != ARGS_QWORD && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); continue; } /* Open a head if one hasn't been opened. */ if (head == NULL) head = mdoc_head_alloc(mdoc, line, ppos, tok); if (ac == ARGS_PHRASE || ac == ARGS_PEND || ac == ARGS_PPHRASE) { /* * If we haven't opened a body yet, rewind the * head; if we have, rewind that instead. */ rew_last(mdoc, body == NULL ? head : body); body = mdoc_body_alloc(mdoc, line, ppos, tok); /* * Process phrases: set whether we're in a * partial-phrase (this effects line handling) * then call down into the phrase parser. */ if (ac == ARGS_PPHRASE) mdoc->flags |= MDOC_PPHRASE; if (ac == ARGS_PEND && lac == ARGS_PPHRASE) mdoc->flags |= MDOC_PPHRASE; parse_rest(mdoc, MDOC_MAX, line, &la, buf); mdoc->flags &= ~MDOC_PPHRASE; continue; } if (macro_or_word(mdoc, tok, line, la, pos, buf, parsed)) break; } if (blk->flags & MDOC_VALID) return; if (head == NULL) head = mdoc_head_alloc(mdoc, line, ppos, tok); if (nl && tok != MDOC_Bd && tok != MDOC_Bl && tok != MDOC_Rs) append_delims(mdoc, line, pos, buf); if (body != NULL) goto out; /* * If there is an open (i.e., unvalidated) sub-block requiring * explicit close-out, postpone switching the current block from * head to body until the rew_pending() call closing out that * sub-block. */ for (n = mdoc->last; n && n != head; n = n->parent) { if (n->flags & MDOC_ENDED) { if ( ! (n->flags & MDOC_VALID)) n->flags |= MDOC_BROKEN; continue; } if (n->type == MDOC_BLOCK && mdoc_macros[n->tok].flags & MDOC_EXPLICIT) { n->flags = MDOC_BROKEN; head->flags = MDOC_ENDED; } } if (head->flags & MDOC_ENDED) return; /* Close out scopes to remain in a consistent state. */ rew_last(mdoc, head); body = mdoc_body_alloc(mdoc, line, ppos, tok); out: if (mdoc->flags & MDOC_FREECOL) { rew_last(mdoc, body); rew_last(mdoc, blk); mdoc->flags &= ~MDOC_FREECOL; } }
static void in_line_argn(MACRO_PROT_ARGS) { struct mdoc_arg *arg; char *p; enum margserr ac; int ntok; int state; /* arg#; -1: not yet open; -2: closed */ int la, maxargs, nl; nl = mdoc->flags & MDOC_NEWLINE; /* * A line macro that has a fixed number of arguments (maxargs). * Only open the scope once the first non-leading-punctuation is * found (unless MDOC_IGNDELIM is noted, like in `Pf'), then * keep it open until the maximum number of arguments are * exhausted. */ switch (tok) { case MDOC_Ap: case MDOC_Ns: case MDOC_Ux: maxargs = 0; break; case MDOC_Bx: case MDOC_Es: case MDOC_Xr: maxargs = 2; break; default: maxargs = 1; break; } mdoc_argv(mdoc, line, tok, &arg, pos, buf); state = -1; p = NULL; for (;;) { la = *pos; ac = mdoc_args(mdoc, line, pos, buf, tok, &p); if (ac == ARGS_WORD && state == -1 && ! (mdoc_macros[tok].flags & MDOC_IGNDELIM) && mdoc_isdelim(p) == DELIM_OPEN) { dword(mdoc, line, la, p, DELIM_OPEN, 0); continue; } if (state == -1 && tok != MDOC_In && tok != MDOC_St && tok != MDOC_Xr) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); state = 0; } if (ac == ARGS_PUNCT || ac == ARGS_EOLN) { if (abs(state) < 2 && tok == MDOC_Pf) mandoc_vmsg(MANDOCERR_PF_SKIP, mdoc->parse, line, ppos, "Pf %s", p == NULL ? "at eol" : p); break; } if (state == maxargs) { rew_elem(mdoc, tok); state = -2; } ntok = (ac == ARGS_QWORD || (tok == MDOC_Pf && state == 0)) ? TOKEN_NONE : lookup(mdoc, tok, line, la, p); if (ntok != TOKEN_NONE) { if (state >= 0) { rew_elem(mdoc, tok); state = -2; } mdoc_macro(mdoc, ntok, line, la, pos, buf); break; } if (ac == ARGS_QWORD || mdoc_macros[tok].flags & MDOC_IGNDELIM || mdoc_isdelim(p) == DELIM_NONE) { if (state == -1) { mdoc_elem_alloc(mdoc, line, ppos, tok, arg); state = 1; } else if (state >= 0) state++; } else if (state >= 0) { rew_elem(mdoc, tok); state = -2; } dword(mdoc, line, la, p, DELIM_MAX, MDOC_JOIN & mdoc_macros[tok].flags); } if (state == -1) { mandoc_msg(MANDOCERR_MACRO_EMPTY, mdoc->parse, line, ppos, mdoc_macronames[tok]); return; } if (state == 0 && tok == MDOC_Pf) append_delims(mdoc, line, pos, buf); if (state >= 0) rew_elem(mdoc, tok); if (nl) append_delims(mdoc, line, pos, buf); }
static void mods(struct tbl_node *tbl, struct tbl_cell *cp, int ln, const char *p, int *pos) { char *endptr; mod: while (p[*pos] == ' ' || p[*pos] == '\t') (*pos)++; /* Row delimiters and cell specifiers end modifier lists. */ if (strchr(".,-=^_ACLNRSaclnrs", p[*pos]) != NULL) return; /* Throw away parenthesised expression. */ if ('(' == p[*pos]) { (*pos)++; while (p[*pos] && ')' != p[*pos]) (*pos)++; if (')' == p[*pos]) { (*pos)++; goto mod; } mandoc_msg(MANDOCERR_TBLLAYOUT_PAR, tbl->parse, ln, *pos, NULL); return; } /* Parse numerical spacing from modifier string. */ if (isdigit((unsigned char)p[*pos])) { cp->spacing = strtoull(p + *pos, &endptr, 10); *pos = endptr - p; goto mod; } switch (tolower((unsigned char)p[(*pos)++])) { case 'b': cp->flags |= TBL_CELL_BOLD; goto mod; case 'd': cp->flags |= TBL_CELL_BALIGN; goto mod; case 'e': cp->flags |= TBL_CELL_EQUAL; goto mod; case 'f': break; case 'i': cp->flags |= TBL_CELL_ITALIC; goto mod; case 'm': mandoc_msg(MANDOCERR_TBLLAYOUT_MOD, tbl->parse, ln, *pos, "m"); goto mod; case 'p': case 'v': if (p[*pos] == '-' || p[*pos] == '+') (*pos)++; while (isdigit((unsigned char)p[*pos])) (*pos)++; goto mod; case 't': cp->flags |= TBL_CELL_TALIGN; goto mod; case 'u': cp->flags |= TBL_CELL_UP; goto mod; case 'w': /* XXX for now, ignore minimal column width */ goto mod; case 'x': cp->flags |= TBL_CELL_WMAX; goto mod; case 'z': cp->flags |= TBL_CELL_WIGN; goto mod; case '|': if (cp->vert < 2) cp->vert++; else mandoc_msg(MANDOCERR_TBLLAYOUT_VERT, tbl->parse, ln, *pos - 1, NULL); goto mod; default: mandoc_vmsg(MANDOCERR_TBLLAYOUT_CHAR, tbl->parse, ln, *pos - 1, "%c", p[*pos - 1]); goto mod; } /* Ignore parenthised font names for now. */ if (p[*pos] == '(') goto mod; /* Support only one-character font-names for now. */ if (p[*pos] == '\0' || (p[*pos + 1] != ' ' && p[*pos + 1] != '.')) { mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse, ln, *pos, "TS %s", p + *pos - 1); if (p[*pos] != '\0') (*pos)++; if (p[*pos] != '\0') (*pos)++; goto mod; } switch (p[(*pos)++]) { case '3': case 'B': cp->flags |= TBL_CELL_BOLD; goto mod; case '2': case 'I': cp->flags |= TBL_CELL_ITALIC; goto mod; case '1': case 'R': goto mod; default: mandoc_vmsg(MANDOCERR_FT_BAD, tbl->parse, ln, *pos - 1, "TS f%c", p[*pos - 1]); goto mod; } }
void man_breakscope(struct roff_man *man, int tok) { struct roff_node *n; /* * An element next line scope is open, * and the new macro is not allowed inside elements. * Delete the element that is being broken. */ if (man->flags & MAN_ELINE && (tok < MAN_TH || ! (man_macros[tok].flags & MAN_NSCOPED))) { n = man->last; assert(n->type != ROFFT_TEXT); if (man_macros[n->tok].flags & MAN_NSCOPED) n = n->parent; mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line, n->pos, "%s breaks %s", roff_name[tok], roff_name[n->tok]); roff_node_delete(man, n); man->flags &= ~MAN_ELINE; } /* * Weird special case: * Switching fill mode closes section headers. */ if (man->flags & MAN_BLINE && (tok == MAN_nf || tok == MAN_fi) && (man->last->tok == MAN_SH || man->last->tok == MAN_SS)) { n = man->last; man_unscope(man, n); roff_body_alloc(man, n->line, n->pos, n->tok); man->flags &= ~MAN_BLINE; } /* * A block header next line scope is open, * and the new macro is not allowed inside block headers. * Delete the block that is being broken. */ if (man->flags & MAN_BLINE && (tok < MAN_TH || man_macros[tok].flags & MAN_BSCOPE)) { n = man->last; if (n->type == ROFFT_TEXT) n = n->parent; if ( ! (man_macros[n->tok].flags & MAN_BSCOPE)) n = n->parent; assert(n->type == ROFFT_HEAD); n = n->parent; assert(n->type == ROFFT_BLOCK); assert(man_macros[n->tok].flags & MAN_SCOPED); mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line, n->pos, "%s breaks %s", roff_name[tok], roff_name[n->tok]); roff_node_delete(man, n); man->flags &= ~MAN_BLINE; } }
/* * Main parse routine for a buffer. * It assumes encoding and line numbering are already set up. * It can recurse directly (for invocations of user-defined * macros, inline equations, and input line traps) * and indirectly (for .so file inclusion). */ static void mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start) { const struct tbl_span *span; struct buf ln; const char *save_file; char *cp; size_t pos; /* byte number in the ln buffer */ enum rofferr rr; int of; int lnn; /* line number in the real file */ int fd; unsigned char c; memset(&ln, 0, sizeof(ln)); lnn = curp->line; pos = 0; while (i < blk.sz) { if (0 == pos && '\0' == blk.buf[i]) break; if (start) { curp->line = lnn; curp->reparse_count = 0; if (lnn < 3 && curp->filenc & MPARSE_UTF8 && curp->filenc & MPARSE_LATIN1) curp->filenc = preconv_cue(&blk, i); } while (i < blk.sz && (start || blk.buf[i] != '\0')) { /* * When finding an unescaped newline character, * leave the character loop to process the line. * Skip a preceding carriage return, if any. */ if ('\r' == blk.buf[i] && i + 1 < blk.sz && '\n' == blk.buf[i + 1]) ++i; if ('\n' == blk.buf[i]) { ++i; ++lnn; break; } /* * Make sure we have space for the worst * case of 11 bytes: "\\[u10ffff]\0" */ if (pos + 11 > ln.sz) resize_buf(&ln, 256); /* * Encode 8-bit input. */ c = blk.buf[i]; if (c & 0x80) { if ( ! (curp->filenc && preconv_encode( &blk, &i, &ln, &pos, &curp->filenc))) { mandoc_vmsg(MANDOCERR_CHAR_BAD, curp, curp->line, pos, "0x%x", c); ln.buf[pos++] = '?'; i++; } continue; } /* * Exclude control characters. */ if (c == 0x7f || (c < 0x20 && c != 0x09)) { mandoc_vmsg(c == 0x00 || c == 0x04 || c > 0x0a ? MANDOCERR_CHAR_BAD : MANDOCERR_CHAR_UNSUPP, curp, curp->line, pos, "0x%x", c); i++; if (c != '\r') ln.buf[pos++] = '?'; continue; } /* Trailing backslash = a plain char. */ if (blk.buf[i] != '\\' || i + 1 == blk.sz) { ln.buf[pos++] = blk.buf[i++]; continue; } /* * Found escape and at least one other character. * When it's a newline character, skip it. * When there is a carriage return in between, * skip that one as well. */ if ('\r' == blk.buf[i + 1] && i + 2 < blk.sz && '\n' == blk.buf[i + 2]) ++i; if ('\n' == blk.buf[i + 1]) { i += 2; ++lnn; continue; } if ('"' == blk.buf[i + 1] || '#' == blk.buf[i + 1]) { i += 2; /* Comment, skip to end of line */ for (; i < blk.sz; ++i) { if ('\n' == blk.buf[i]) { ++i; ++lnn; break; } } /* Backout trailing whitespaces */ for (; pos > 0; --pos) { if (ln.buf[pos - 1] != ' ') break; if (pos > 2 && ln.buf[pos - 2] == '\\') break; } break; } /* Catch escaped bogus characters. */ c = (unsigned char) blk.buf[i+1]; if ( ! (isascii(c) && (isgraph(c) || isblank(c)))) { mandoc_vmsg(MANDOCERR_CHAR_BAD, curp, curp->line, pos, "0x%x", c); i += 2; ln.buf[pos++] = '?'; continue; } /* Some other escape sequence, copy & cont. */ ln.buf[pos++] = blk.buf[i++]; ln.buf[pos++] = blk.buf[i++]; } if (pos >= ln.sz) resize_buf(&ln, 256); ln.buf[pos] = '\0'; /* * A significant amount of complexity is contained by * the roff preprocessor. It's line-oriented but can be * expressed on one line, so we need at times to * readjust our starting point and re-run it. The roff * preprocessor can also readjust the buffers with new * data, so we pass them in wholesale. */ of = 0; /* * Maintain a lookaside buffer of all parsed lines. We * only do this if mparse_keep() has been invoked (the * buffer may be accessed with mparse_getkeep()). */ if (curp->secondary) { curp->secondary->buf = mandoc_realloc( curp->secondary->buf, curp->secondary->sz + pos + 2); memcpy(curp->secondary->buf + curp->secondary->sz, ln.buf, pos); curp->secondary->sz += pos; curp->secondary->buf [curp->secondary->sz] = '\n'; curp->secondary->sz++; curp->secondary->buf [curp->secondary->sz] = '\0'; } rerun: rr = roff_parseln(curp->roff, curp->line, &ln, &of); switch (rr) { case ROFF_REPARSE: if (REPARSE_LIMIT >= ++curp->reparse_count) mparse_buf_r(curp, ln, of, 0); else mandoc_msg(MANDOCERR_ROFFLOOP, curp, curp->line, pos, NULL); pos = 0; continue; case ROFF_APPEND: pos = strlen(ln.buf); continue; case ROFF_RERUN: goto rerun; case ROFF_IGN: pos = 0; continue; case ROFF_SO: if ( ! (curp->options & MPARSE_SO) && (i >= blk.sz || blk.buf[i] == '\0')) { curp->sodest = mandoc_strdup(ln.buf + of); free(ln.buf); return; } /* * We remove `so' clauses from our lookaside * buffer because we're going to descend into * the file recursively. */ if (curp->secondary) curp->secondary->sz -= pos + 1; save_file = curp->file; if ((fd = mparse_open(curp, ln.buf + of)) != -1) { mparse_readfd(curp, fd, ln.buf + of); close(fd); curp->file = save_file; } else { curp->file = save_file; mandoc_vmsg(MANDOCERR_SO_FAIL, curp, curp->line, pos, ".so %s", ln.buf + of); ln.sz = mandoc_asprintf(&cp, ".sp\nSee the file %s.\n.sp", ln.buf + of); free(ln.buf); ln.buf = cp; of = 0; mparse_buf_r(curp, ln, of, 0); } pos = 0; continue; default: break; } /* * If input parsers have not been allocated, do so now. * We keep these instanced between parsers, but set them * locally per parse routine since we can use different * parsers with each one. */ if (curp->man == NULL || curp->man->macroset == MACROSET_NONE) choose_parser(curp); /* * Lastly, push down into the parsers themselves. * If libroff returns ROFF_TBL, then add it to the * currently open parse. Since we only get here if * there does exist data (see tbl_data.c), we're * guaranteed that something's been allocated. * Do the same for ROFF_EQN. */ if (rr == ROFF_TBL) while ((span = roff_span(curp->roff)) != NULL) roff_addtbl(curp->man, span); else if (rr == ROFF_EQN) roff_addeqn(curp->man, roff_eqn(curp->roff)); else if ((curp->man->macroset == MACROSET_MDOC ? mdoc_parseln(curp->man, curp->line, ln.buf, of) : man_parseln(curp->man, curp->line, ln.buf, of)) == 2) break; /* Temporary buffers typically are not full. */ if (0 == start && '\0' == blk.buf[i]) break; /* Start the next input line. */ pos = 0; } free(ln.buf); }
static int man_pmacro(struct man *man, int ln, char *buf, int offs) { int i, ppos; enum mant tok; char mac[5]; struct man_node *n; if ('"' == buf[offs]) { man_pmsg(man, ln, offs, MANDOCERR_BADCOMMENT); return(1); } else if ('\0' == buf[offs]) return(1); ppos = offs; /* * Copy the first word into a nil-terminated buffer. * Stop copying when a tab, space, or eoln is encountered. */ i = 0; while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] && '\t' != buf[offs]) mac[i++] = buf[offs++]; mac[i] = '\0'; tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX; if (MAN_MAX == tok) { mandoc_vmsg(MANDOCERR_MACRO, man->parse, ln, ppos, "%s", buf + ppos - 1); return(1); } /* The macro is sane. Jump to the next word. */ while (buf[offs] && ' ' == buf[offs]) offs++; /* * Trailing whitespace. Note that tabs are allowed to be passed * into the parser as "text", so we only warn about spaces here. */ if ('\0' == buf[offs] && ' ' == buf[offs - 1]) man_pmsg(man, ln, offs - 1, MANDOCERR_EOLNSPACE); /* * Remove prior ELINE macro, as it's being clobbered by a new * macro. Note that NSCOPED macros do not close out ELINE * macros---they don't print text---so we let those slip by. */ if ( ! (MAN_NSCOPED & man_macros[tok].flags) && man->flags & MAN_ELINE) { n = man->last; assert(MAN_TEXT != n->type); /* Remove repeated NSCOPED macros causing ELINE. */ if (MAN_NSCOPED & man_macros[n->tok].flags) n = n->parent; mandoc_vmsg(MANDOCERR_LINESCOPE, man->parse, n->line, n->pos, "%s breaks %s", man_macronames[tok], man_macronames[n->tok]); man_node_delete(man, n); man->flags &= ~MAN_ELINE; } /* * Remove prior BLINE macro that is being clobbered. */ if ((man->flags & MAN_BLINE) && (MAN_BSCOPE & man_macros[tok].flags)) { n = man->last; /* Might be a text node like 8 in * .TP 8 * .SH foo */ if (MAN_TEXT == n->type) n = n->parent; /* Remove element that didn't end BLINE, if any. */ if ( ! (MAN_BSCOPE & man_macros[n->tok].flags)) n = n->parent; assert(MAN_HEAD == n->type); n = n->parent; assert(MAN_BLOCK == n->type); assert(MAN_SCOPED & man_macros[n->tok].flags); mandoc_vmsg(MANDOCERR_LINESCOPE, man->parse, n->line, n->pos, "%s breaks %s", man_macronames[tok], man_macronames[n->tok]); man_node_delete(man, n); man->flags &= ~MAN_BLINE; } /* * Save the fact that we're in the next-line for a block. In * this way, embedded roff instructions can "remember" state * when they exit. */ if (MAN_BLINE & man->flags) man->flags |= MAN_BPLINE; /* Call to handler... */ assert(man_macros[tok].fp); if ( ! (*man_macros[tok].fp)(man, tok, ln, ppos, &offs, buf)) goto err; /* * We weren't in a block-line scope when entering the * above-parsed macro, so return. */ if ( ! (MAN_BPLINE & man->flags)) { man->flags &= ~MAN_ILINE; return(1); } man->flags &= ~MAN_BPLINE; /* * If we're in a block scope, then allow this macro to slip by * without closing scope around it. */ if (MAN_ILINE & man->flags) { man->flags &= ~MAN_ILINE; return(1); } /* * If we've opened a new next-line element scope, then return * now, as the next line will close out the block scope. */ if (MAN_ELINE & man->flags) return(1); /* Close out the block scope opened in the prior line. */ assert(MAN_BLINE & man->flags); man->flags &= ~MAN_BLINE; if ( ! man_unscope(man, man->last->parent, MANDOCERR_MAX)) return(0); return(man_body_alloc(man, ln, ppos, man->last->tok)); err: /* Error out. */ man->flags |= MAN_HALT; return(0); }
static int man_pmacro(struct man *man, int ln, char *buf, int offs) { char mac[5]; struct man_node *n; enum mant tok; int i, ppos; int bline; if ('"' == buf[offs]) { mandoc_msg(MANDOCERR_COMMENT_BAD, man->parse, ln, offs, NULL); return(1); } else if ('\0' == buf[offs]) return(1); ppos = offs; /* * Copy the first word into a nil-terminated buffer. * Stop copying when a tab, space, or eoln is encountered. */ i = 0; while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] && '\t' != buf[offs]) mac[i++] = buf[offs++]; mac[i] = '\0'; tok = (i > 0 && i < 4) ? man_hash_find(mac) : MAN_MAX; if (MAN_MAX == tok) { mandoc_msg(MANDOCERR_MACRO, man->parse, ln, ppos, buf + ppos - 1); return(1); } /* The macro is sane. Jump to the next word. */ while (buf[offs] && ' ' == buf[offs]) offs++; /* * Trailing whitespace. Note that tabs are allowed to be passed * into the parser as "text", so we only warn about spaces here. */ if ('\0' == buf[offs] && ' ' == buf[offs - 1]) mandoc_msg(MANDOCERR_SPACE_EOL, man->parse, ln, offs - 1, NULL); /* * Remove prior ELINE macro, as it's being clobbered by a new * macro. Note that NSCOPED macros do not close out ELINE * macros---they don't print text---so we let those slip by. */ if ( ! (MAN_NSCOPED & man_macros[tok].flags) && man->flags & MAN_ELINE) { n = man->last; assert(MAN_TEXT != n->type); /* Remove repeated NSCOPED macros causing ELINE. */ if (MAN_NSCOPED & man_macros[n->tok].flags) n = n->parent; mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line, n->pos, "%s breaks %s", man_macronames[tok], man_macronames[n->tok]); man_node_delete(man, n); man->flags &= ~MAN_ELINE; } /* * Remove prior BLINE macro that is being clobbered. */ if ((man->flags & MAN_BLINE) && (MAN_BSCOPE & man_macros[tok].flags)) { n = man->last; /* Might be a text node like 8 in * .TP 8 * .SH foo */ if (MAN_TEXT == n->type) n = n->parent; /* Remove element that didn't end BLINE, if any. */ if ( ! (MAN_BSCOPE & man_macros[n->tok].flags)) n = n->parent; assert(MAN_HEAD == n->type); n = n->parent; assert(MAN_BLOCK == n->type); assert(MAN_SCOPED & man_macros[n->tok].flags); mandoc_vmsg(MANDOCERR_BLK_LINE, man->parse, n->line, n->pos, "%s breaks %s", man_macronames[tok], man_macronames[n->tok]); man_node_delete(man, n); man->flags &= ~MAN_BLINE; } /* Remember whether we are in next-line scope for a block head. */ bline = man->flags & MAN_BLINE; /* Call to handler... */ assert(man_macros[tok].fp); if ( ! (*man_macros[tok].fp)(man, tok, ln, ppos, &offs, buf)) return(0); /* In quick mode (for mandocdb), abort after the NAME section. */ if (man->quick && MAN_SH == tok) { n = man->last; if (MAN_BODY == n->type && strcmp(n->prev->child->string, "NAME")) return(2); } /* * If we are in a next-line scope for a block head, * close it out now and switch to the body, * unless the next-line scope is allowed to continue. */ if ( ! bline || man->flags & MAN_ELINE || man_macros[tok].flags & MAN_NSCOPED) return(1); assert(MAN_BLINE & man->flags); man->flags &= ~MAN_BLINE; if ( ! man_unscope(man, man->last->parent)) return(0); return(man_body_alloc(man, ln, ppos, man->last->tok)); }
void in_line_eoln(MACRO_PROT_ARGS) { int la; char *p; struct roff_node *n; roff_elem_alloc(man, line, ppos, tok); n = man->last; for (;;) { if (buf[*pos] != '\0' && (tok == MAN_br || tok == MAN_fi || tok == MAN_nf)) { mandoc_vmsg(MANDOCERR_ARG_SKIP, man->parse, line, *pos, "%s %s", man_macronames[tok], buf + *pos); break; } if (buf[*pos] != '\0' && man->last != n && (tok == MAN_PD || tok == MAN_ft || tok == MAN_sp)) { mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse, line, *pos, "%s ... %s", man_macronames[tok], buf + *pos); break; } la = *pos; if ( ! man_args(man, line, pos, buf, &p)) break; if (man_macros[tok].flags & MAN_JOIN && man->last->type == ROFFT_TEXT) roff_word_append(man, p); else roff_word_alloc(man, line, la, p); } /* * Append MAN_EOS in case the last snipped argument * ends with a dot, e.g. `.IR syslog (3).' */ if (n != man->last && mandoc_eos(man->last->string, strlen(man->last->string))) man->last->flags |= MAN_EOS; /* * If no arguments are specified and this is MAN_SCOPED (i.e., * next-line scoped), then set our mode to indicate that we're * waiting for terms to load into our context. */ if (n == man->last && man_macros[tok].flags & MAN_SCOPED) { assert( ! (man_macros[tok].flags & MAN_NSCOPED)); man->flags |= MAN_ELINE; return; } assert(man->last->type != ROFFT_ROOT); man->next = ROFF_NEXT_SIBLING; /* Rewind our element scope. */ for ( ; man->last; man->last = man->last->parent) { man_state(man, man->last); if (man->last == n) break; } }
/* * Parse a macro line, that is, a line beginning with the control * character. */ static int mdoc_pmacro(struct mdoc *m, int ln, char *buf, int offs) { enum mdoct tok; int i, sv; char mac[5]; struct mdoc_node *n; /* Empty post-control lines are ignored. */ if ('"' == buf[offs]) { mdoc_pmsg(m, ln, offs, MANDOCERR_BADCOMMENT); return(1); } else if ('\0' == buf[offs]) return(1); sv = offs; /* * Copy the first word into a nil-terminated buffer. * Stop copying when a tab, space, or eoln is encountered. */ i = 0; while (i < 4 && '\0' != buf[offs] && ' ' != buf[offs] && '\t' != buf[offs]) mac[i++] = buf[offs++]; mac[i] = '\0'; tok = (i > 1 || i < 4) ? mdoc_hash_find(mac) : MDOC_MAX; if (MDOC_MAX == tok) { mandoc_vmsg(MANDOCERR_MACRO, m->parse, ln, sv, "%s", buf + sv - 1); return(1); } /* Disregard the first trailing tab, if applicable. */ if ('\t' == buf[offs]) offs++; /* Jump to the next non-whitespace word. */ while (buf[offs] && ' ' == buf[offs]) offs++; /* * Trailing whitespace. Note that tabs are allowed to be passed * into the parser as "text", so we only warn about spaces here. */ if ('\0' == buf[offs] && ' ' == buf[offs - 1]) mdoc_pmsg(m, ln, offs - 1, MANDOCERR_EOLNSPACE); /* * If an initial macro or a list invocation, divert directly * into macro processing. */ if (NULL == m->last || MDOC_It == tok || MDOC_El == tok) { if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) goto err; return(1); } n = m->last; assert(m->last); /* * If the first macro of a `Bl -column', open an `It' block * context around the parsed macro. */ if (MDOC_Bl == n->tok && MDOC_BODY == n->type && LIST_column == n->norm->Bl.type) { m->flags |= MDOC_FREECOL; if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) goto err; return(1); } /* * If we're following a block-level `It' within a `Bl -column' * context (perhaps opened in the above block or in ptext()), * then open an `It' block context around the parsed macro. */ if (MDOC_It == n->tok && MDOC_BLOCK == n->type && NULL != n->parent && MDOC_Bl == n->parent->tok && LIST_column == n->parent->norm->Bl.type) { m->flags |= MDOC_FREECOL; if ( ! mdoc_macro(m, MDOC_It, ln, sv, &sv, buf)) goto err; return(1); } /* Normal processing of a macro. */ if ( ! mdoc_macro(m, tok, ln, sv, &offs, buf)) goto err; return(1); err: /* Error out. */ m->flags |= MDOC_HALT; return(0); }
/* * We are trying to close a block identified by tok, * but the child block *broken is still open. * Thus, postpone closing the tok block * until the rew_sub call closing *broken. */ static int make_pending(struct mdoc_node *broken, enum mdoct tok, struct mdoc *m, int line, int ppos) { struct mdoc_node *breaker; /* * Iterate backwards, searching for the block matching tok, * that is, the block breaking the *broken block. */ for (breaker = broken->parent; breaker; breaker = breaker->parent) { /* * If the *broken block had already been broken before * and we encounter its breaker, make the tok block * pending on the inner breaker. * Graphically, "[A breaker=[B broken=[C->B B] tok=A] C]" * becomes "[A broken=[B [C->B B] tok=A] C]" * and finally "[A [B->A [C->B B] A] C]". */ if (breaker == broken->pending) { broken = breaker; continue; } if (REWIND_THIS != rew_dohalt(tok, MDOC_BLOCK, breaker)) continue; if (MDOC_BODY == broken->type) broken = broken->parent; /* * Found the breaker. * If another, outer breaker is already pending on * the *broken block, we must not clobber the link * to the outer breaker, but make it pending on the * new, now inner breaker. * Graphically, "[A breaker=[B broken=[C->A A] tok=B] C]" * becomes "[A breaker=[B->A broken=[C A] tok=B] C]" * and finally "[A [B->A [C->B A] B] C]". */ if (broken->pending) { struct mdoc_node *taker; /* * If the breaker had also been broken before, * it cannot take on the outer breaker itself, * but must hand it on to its own breakers. * Graphically, this is the following situation: * "[A [B breaker=[C->B B] broken=[D->A A] tok=C] D]" * "[A taker=[B->A breaker=[C->B B] [D->C A] C] D]" */ taker = breaker; while (taker->pending) taker = taker->pending; taker->pending = broken->pending; } broken->pending = breaker; mandoc_vmsg(MANDOCERR_SCOPENEST, m->parse, line, ppos, "%s breaks %s", mdoc_macronames[tok], mdoc_macronames[broken->tok]); return(1); } /* * Found no matching block for tok. * Are you trying to close a block that is not open? */ return(0); }
static int blk_part_imp(MACRO_PROT_ARGS) { int la, nl; enum mdoct ntok; enum margserr ac; char *p; struct mdoc_node *blk; /* saved block context */ struct mdoc_node *body; /* saved body context */ struct mdoc_node *n; nl = MDOC_NEWLINE & m->flags; /* * A macro that spans to the end of the line. This is generally * (but not necessarily) called as the first macro. The block * has a head as the immediate child, which is always empty, * followed by zero or more opening punctuation nodes, then the * body (which may be empty, depending on the macro), then zero * or more closing punctuation nodes. */ if ( ! mdoc_block_alloc(m, line, ppos, tok, NULL)) return(0); blk = m->last; if ( ! mdoc_head_alloc(m, line, ppos, tok)) return(0); if ( ! rew_sub(MDOC_HEAD, m, tok, line, ppos)) return(0); /* * Open the body scope "on-demand", that is, after we've * processed all our the leading delimiters (open parenthesis, * etc.). */ for (body = NULL; ; ) { la = *pos; ac = mdoc_args(m, line, pos, buf, tok, &p); if (ARGS_ERROR == ac) return(0); if (ARGS_EOLN == ac) break; if (ARGS_PUNCT == ac) break; if (NULL == body && ARGS_QWORD != ac && DELIM_OPEN == mdoc_isdelim(p)) { if ( ! dword(m, line, la, p, DELIM_OPEN)) return(0); continue; } if (NULL == body) { if ( ! mdoc_body_alloc(m, line, ppos, tok)) return(0); body = m->last; } ntok = ARGS_QWORD == ac ? MDOC_MAX : lookup(tok, p); if (MDOC_MAX == ntok) { if ( ! dword(m, line, la, p, DELIM_MAX)) return(0); continue; } if ( ! mdoc_macro(m, ntok, line, la, pos, buf)) return(0); break; } /* Clean-ups to leave in a consistent state. */ if (NULL == body) { if ( ! mdoc_body_alloc(m, line, ppos, tok)) return(0); body = m->last; } for (n = body->child; n && n->next; n = n->next) /* Do nothing. */ ; /* * End of sentence spacing: if the last node is a text node and * has a trailing period, then mark it as being end-of-sentence. */ if (n && MDOC_TEXT == n->type && n->string) if (mandoc_eos(n->string, strlen(n->string), 1)) n->flags |= MDOC_EOS; /* Up-propagate the end-of-space flag. */ if (n && (MDOC_EOS & n->flags)) { body->flags |= MDOC_EOS; body->parent->flags |= MDOC_EOS; } /* * If there is an open sub-block requiring explicit close-out, * postpone closing out the current block * until the rew_sub() call closing out the sub-block. */ for (n = m->last; n && n != body && n != blk->parent; n = n->parent) { if (MDOC_BLOCK == n->type && MDOC_EXPLICIT & mdoc_macros[n->tok].flags && ! (MDOC_VALID & n->flags)) { make_pending(n, tok, m, line, ppos); if ( ! mdoc_endbody_alloc(m, line, ppos, tok, body, ENDBODY_NOSPACE)) return(0); return(1); } } /* * If we can't rewind to our body, then our scope has already * been closed by another macro (like `Oc' closing `Op'). This * is ugly behaviour nodding its head to OpenBSD's overwhelming * crufty use of `Op' breakage. */ if (n != body) mandoc_vmsg(MANDOCERR_SCOPENEST, m->parse, line, ppos, "%s broken", mdoc_macronames[tok]); if (n && ! rew_sub(MDOC_BODY, m, tok, line, ppos)) return(0); /* Standard appending of delimiters. */ if (nl && ! append_delims(m, line, pos, buf)) return(0); /* Rewind scope, if applicable. */ if (n && ! rew_sub(MDOC_BLOCK, m, tok, line, ppos)) return(0); return(1); }
static void post_TH(CHKARGS) { struct man_node *nb; const char *p; free(man->meta.title); free(man->meta.vol); free(man->meta.source); free(man->meta.msec); free(man->meta.date); man->meta.title = man->meta.vol = man->meta.date = man->meta.msec = man->meta.source = NULL; nb = n; /* ->TITLE<- MSEC DATE SOURCE VOL */ n = n->child; if (n && n->string) { for (p = n->string; '\0' != *p; p++) { /* Only warn about this once... */ if (isalpha((unsigned char)*p) && ! isupper((unsigned char)*p)) { mandoc_vmsg(MANDOCERR_TITLE_CASE, man->parse, n->line, n->pos + (p - n->string), "TH %s", n->string); break; } } man->meta.title = mandoc_strdup(n->string); } else { man->meta.title = mandoc_strdup(""); mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse, nb->line, nb->pos, "TH"); } /* TITLE ->MSEC<- DATE SOURCE VOL */ if (n) n = n->next; if (n && n->string) man->meta.msec = mandoc_strdup(n->string); else { man->meta.msec = mandoc_strdup(""); mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse, nb->line, nb->pos, "TH %s", man->meta.title); } /* TITLE MSEC ->DATE<- SOURCE VOL */ if (n) n = n->next; if (n && n->string && '\0' != n->string[0]) { man->meta.date = man->quick ? mandoc_strdup(n->string) : mandoc_normdate(man->parse, n->string, n->line, n->pos); } else { man->meta.date = mandoc_strdup(""); mandoc_msg(MANDOCERR_DATE_MISSING, man->parse, n ? n->line : nb->line, n ? n->pos : nb->pos, "TH"); } /* TITLE MSEC DATE ->SOURCE<- VOL */ if (n && (n = n->next)) man->meta.source = mandoc_strdup(n->string); else if (man->defos != NULL) man->meta.source = mandoc_strdup(man->defos); /* TITLE MSEC DATE SOURCE ->VOL<- */ /* If missing, use the default VOL name for MSEC. */ if (n && (n = n->next)) man->meta.vol = mandoc_strdup(n->string); else if ('\0' != man->meta.msec[0] && (NULL != (p = mandoc_a2msec(man->meta.msec)))) man->meta.vol = mandoc_strdup(p); if (n != NULL && (n = n->next) != NULL) mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse, n->line, n->pos, "TH ... %s", n->string); /* * Remove the `TH' node after we've processed it for our * meta-data. */ man_node_delete(man, man->last); }