/* $Id: macro.c,v 1.8 2019/04/10 14:22:37 schwarze Exp $ */ /* * Copyright (c) 2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * 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. */ #include #include #include #include #include "node.h" #include "macro.h" /* * The implementation of the macro line formatter, * a part of the mdoc(7) formatter. */ void macro_open(struct format *f, const char *name) { switch (f->linestate) { case LINE_TEXT: putchar('\n'); /* FALLTHROUGH */ case LINE_NEW: putchar('.'); f->linestate = LINE_MACRO; break; case LINE_MACRO: putchar(' '); if (f->spc == 0) fputs("Ns ", stdout); break; } fputs(name, stdout); } void macro_close(struct format *f) { if (f->linestate == LINE_NEW) return; putchar('\n'); f->linestate = LINE_NEW; } void macro_line(struct format *f, const char *name) { macro_close(f); macro_open(f, name); macro_close(f); } /* * At the end of a macro, decide whether the line needs to remain open * because the next node follows without intervening whitespace; * otherwise, close the line. */ void macro_closepunct(struct format *f, struct pnode *pn) { char *cp; if ((pn = TAILQ_NEXT(pn, child)) != NULL && pn->spc == 0) { /* * If a non-text node follows without intervening * whitespace, the handler of that node will decide * whether and how to suppress whitespace. To allow * that, the macro line needs to remain open. */ if (pn->node != NODE_TEXT && pn->node != NODE_ESCAPE) return; /* * Give closing punctuation * in the form of trailing macro arguments. */ while (*pn->b != '\0' && strchr("!),.:;?]", *pn->b) != NULL) { putchar(' '); putchar(*pn->b); pn->b++; pn->bsz--; } /* * Text follows without intervening whitespace. * Append the first word with .Ns. */ if (*pn->b != '\0' && isspace((unsigned char)*pn->b) == 0) { fputs(" Ns", stdout); for (cp = pn->b; *cp != '\0'; cp++) if (isspace((unsigned char)*cp)) break; *cp = '\0'; macro_addarg(f, pn->b, ARG_SPACE); pn->bsz -= cp - pn->b; pn->b = cp; if (pn->bsz > 0) { pn->b++; pn->bsz--; pn->spc = 1; } } /* Skip whitespace after the first word. */ while (isspace((unsigned char)*pn->b)) { pn->b++; pn->bsz--; pn->spc = 1; } } macro_close(f); } /* * Print an argument string on a macro line, collapsing whitespace. */ void macro_addarg(struct format *f, const char *arg, int flags) { const char *cp; assert(f->linestate == LINE_MACRO); /* Quote if requested and necessary. */ if ((flags & (ARG_SINGLE | ARG_QUOTED)) == ARG_SINGLE) { for (cp = arg; *cp != '\0'; cp++) if (isspace((unsigned char)*cp)) break; if (*cp != '\0') { if (flags & ARG_SPACE) { putchar(' '); flags &= ~ ARG_SPACE; } putchar('"'); flags = ARG_QUOTED; } } for (cp = arg; *cp != '\0'; cp++) { /* Collapse whitespace. */ if (isspace((unsigned char)*cp)) { flags |= ARG_SPACE; continue; } else if (flags & ARG_SPACE) { putchar(' '); flags &= ~ ARG_SPACE; } /* Escape us if we look like a macro. */ if ((flags & ARG_QUOTED) == 0 && (cp == arg || isspace((unsigned char)cp[-1])) && isupper((unsigned char)cp[0]) && islower((unsigned char)cp[1]) && (cp[2] == '\0' || cp[2] == ' ' || (islower((unsigned char)cp[2]) && (cp[3] == '\0' || cp[3] == ' ')))) fputs("\\&", stdout); if (*cp == '"') fputs("\\(dq", stdout); else if (flags & ARG_UPPER) putchar(toupper((unsigned char)*cp)); else putchar(*cp); if (*cp == '\\') putchar('e'); } } void macro_argline(struct format *f, const char *name, const char *arg) { macro_open(f, name); macro_addarg(f, arg, ARG_SPACE); macro_close(f); } /* * Recursively append text from the children of a node to a macro line. */ void macro_addnode(struct format *f, struct pnode *n, int flags) { struct pnode *nc; int quote_now; assert(f->linestate == LINE_MACRO); /* * If this node or its only child is a text node, just add * that text, letting macro_addarg() decide about quoting. */ while ((nc = TAILQ_FIRST(&n->childq)) != NULL && TAILQ_NEXT(nc, child) == NULL) n = nc; if (n->node == NODE_TEXT || n->node == NODE_ESCAPE) { macro_addarg(f, n->b, flags); return; } /* * If we want the argument quoted and are not already * in a quoted context, quote now. */ quote_now = 0; if (flags & ARG_SINGLE) { if ((flags & ARG_QUOTED) == 0) { if (flags & ARG_SPACE) { putchar(' '); flags &= ~ARG_SPACE; } putchar('"'); flags |= ARG_QUOTED; quote_now = 1; } flags &= ~ARG_SINGLE; } /* * Iterate to child and sibling nodes, * inserting whitespace between nodes. */ while (nc != NULL) { macro_addnode(f, nc, flags); nc = TAILQ_NEXT(nc, child); flags |= ARG_SPACE; } if (quote_now) putchar('"'); } void macro_nodeline(struct format *f, const char *name, struct pnode *n, int flags) { macro_open(f, name); macro_addnode(f, n, ARG_SPACE | flags); macro_close(f); } /* * Print a word on the current text line if one is open, or on a new text * line otherwise. The flag ARG_SPACE inserts spaces between words. */ void print_text(struct format *f, const char *word, int flags) { switch (f->linestate) { case LINE_NEW: break; case LINE_TEXT: if (flags & ARG_SPACE) putchar(' '); break; case LINE_MACRO: macro_close(f); break; } fputs(word, stdout); f->linestate = LINE_TEXT; } /* * Recursively print the content of a node on a text line. */ void print_textnode(struct format *f, struct pnode *n) { struct pnode *nc; if (n->node == NODE_TEXT || n->node == NODE_ESCAPE) print_text(f, n->b, ARG_SPACE); else TAILQ_FOREACH(nc, &n->childq, child) print_textnode(f, nc); }