Annotation of mandoc/mdocterm.c, Revision 1.47
1.47 ! kristaps 1: /* $Id: mdocterm.c,v 1.46 2009/03/16 22:19:19 kristaps Exp $ */
1.1 kristaps 2: /*
1.47 ! kristaps 3: * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@openbsd.org>
1.1 kristaps 4: *
5: * Permission to use, copy, modify, and distribute this software for any
6: * purpose with or without fee is hereby granted, provided that the
7: * above copyright notice and this permission notice appear in all
8: * copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11: * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12: * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13: * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14: * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15: * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16: * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17: * PERFORMANCE OF THIS SOFTWARE.
18: */
1.37 kristaps 19: #include <sys/types.h>
20:
1.1 kristaps 21: #include <assert.h>
1.3 kristaps 22: #include <ctype.h>
1.1 kristaps 23: #include <err.h>
24: #include <getopt.h>
1.3 kristaps 25: #include <stdio.h>
1.1 kristaps 26: #include <stdlib.h>
1.3 kristaps 27: #include <string.h>
1.43 kristaps 28: #include <unistd.h>
1.5 kristaps 29:
1.2 kristaps 30: #include "mmain.h"
1.1 kristaps 31: #include "term.h"
32:
1.43 kristaps 33: struct nroffopt {
34: int fl_h;
35: int fl_i;
36: char *arg_m;
37: char *arg_n;
38: char *arg_o;
39: char *arg_r;
40: char *arg_T;
41: struct termp *termp; /* Ephemeral. */
42: };
43:
1.46 kristaps 44: __dead void punt(struct nroffopt *, char *);
1.43 kristaps 45: static int option(void *, int, char *);
46: static int optsopt(struct termp *, char *);
1.3 kristaps 47: static void body(struct termp *,
1.12 kristaps 48: struct termpair *,
1.3 kristaps 49: const struct mdoc_meta *,
50: const struct mdoc_node *);
51: static void header(struct termp *,
52: const struct mdoc_meta *);
53: static void footer(struct termp *,
54: const struct mdoc_meta *);
55:
56: static void pword(struct termp *, const char *, size_t);
1.25 kristaps 57: static void pescape(struct termp *, const char *,
58: size_t *, size_t);
59: static void nescape(struct termp *,
1.12 kristaps 60: const char *, size_t);
1.3 kristaps 61: static void chara(struct termp *, char);
1.25 kristaps 62: static void stringa(struct termp *,
63: const char *, size_t);
1.38 kristaps 64: static void sanity(const struct mdoc_node *);
1.3 kristaps 65:
1.1 kristaps 66: int
67: main(int argc, char *argv[])
68: {
1.34 kristaps 69: struct mmain *p;
1.2 kristaps 70: const struct mdoc *mdoc;
1.43 kristaps 71: struct nroffopt nroff;
1.34 kristaps 72: struct termp termp;
1.43 kristaps 73: int c;
74: char *in;
1.2 kristaps 75:
1.42 kristaps 76: (void)memset(&termp, 0, sizeof(struct termp));
1.43 kristaps 77: (void)memset(&nroff, 0, sizeof(struct nroffopt));
1.42 kristaps 78:
1.43 kristaps 79: termp.maxrmargin = termp.rmargin = 78; /* FIXME */
80: termp.maxcols = 1024; /* FIXME */
1.42 kristaps 81: termp.flags = TERMP_NOSPACE;
1.46 kristaps 82: termp.symtab = ascii2htab();
1.43 kristaps 83:
84: nroff.termp = &termp;
1.42 kristaps 85:
1.2 kristaps 86: p = mmain_alloc();
1.43 kristaps 87:
1.42 kristaps 88: c = mmain_getopt(p, argc, argv, "[-Ooption...]",
1.43 kristaps 89: "[infile]", "him:n:o:r:T:O:", &nroff, option);
1.1 kristaps 90:
1.43 kristaps 91: /* FIXME: this needs to accept multiple outputs. */
92: argv += c;
93: if ((argc -= c) > 0)
94: in = *argv++;
95: else
96: in = "-";
97:
98: mmain_prepare(p, in);
99:
100: if (NULL == (mdoc = mmain_process(p))) {
101: if (TERMP_NOPUNT & termp.iflags)
102: mmain_exit(p, 1);
103: mmain_free(p);
104: punt(&nroff, in);
105: /* NOTREACHED */
106: }
1.3 kristaps 107:
108: if (NULL == (termp.buf = malloc(termp.maxcols)))
109: err(1, "malloc");
110:
1.15 kristaps 111: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 112: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 113: footer(&termp, mdoc_meta(mdoc));
114:
115: free(termp.buf);
116:
117: mmain_exit(p, 0);
118: /* NOTREACHED */
119: }
120:
121:
1.43 kristaps 122: static int
123: optsopt(struct termp *p, char *arg)
124: {
125: char *v;
1.45 kristaps 126: char *toks[] = { "nopunt", NULL };
1.43 kristaps 127:
128: while (*arg)
129: switch (getsubopt(&arg, toks, &v)) {
130: case (0):
131: p->iflags |= TERMP_NOPUNT;
132: break;
133: default:
134: warnx("unknown -O argument");
135: return(0);
136: }
137:
138: return(1);
139: }
140:
141:
142: static int
143: option(void *ptr, int c, char *arg)
1.42 kristaps 144: {
1.43 kristaps 145: struct termp *termp;
146: struct nroffopt *nroff;
1.42 kristaps 147:
1.43 kristaps 148: nroff = (struct nroffopt *)ptr;
149: termp = nroff->termp;
1.42 kristaps 150:
1.43 kristaps 151: switch (c) {
152: case ('h'):
153: nroff->fl_h = 1;
154: break;
155: case ('i'):
156: nroff->fl_i = 1;
157: break;
158: case ('m'):
159: nroff->arg_m = arg;
160: break;
161: case ('n'):
162: nroff->arg_n = arg;
163: break;
164: case ('o'):
165: nroff->arg_o = arg;
166: break;
167: case ('r'):
168: nroff->arg_r = arg;
169: break;
170: case ('T'):
171: nroff->arg_T = arg;
172: break;
173: case ('O'):
174: return(optsopt(termp, arg));
175: default:
176: break;
1.42 kristaps 177: }
178:
1.43 kristaps 179: return(1);
1.42 kristaps 180: }
181:
182:
1.25 kristaps 183: /*
184: * Flush a line of text. A "line" is loosely defined as being something
185: * that should be followed by a newline, regardless of whether it's
186: * broken apart by newlines getting there. A line can also be a
187: * fragment of a columnar list.
188: *
189: * Specifically, a line is whatever's in p->buf of length p->col, which
190: * is zeroed after this function returns.
191: *
192: * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
193: * critical importance here. Their behaviour follows:
194: *
195: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
196: * offset value. This is useful when doing columnar lists where the
197: * prior column has right-padded.
198: *
199: * - TERMP_NOBREAK: this is the most important and is used when making
200: * columns. In short: don't print a newline and instead pad to the
201: * right margin. Used in conjunction with TERMP_NOLPAD.
202: *
203: * In-line line breaking:
204: *
205: * If TERMP_NOBREAK is specified and the line overruns the right
206: * margin, it will break and pad-right to the right margin after
207: * writing. If maxrmargin is violated, it will break and continue
208: * writing from the right-margin, which will lead to the above
209: * scenario upon exit.
210: *
211: * Otherwise, the line will break at the right margin. Extremely long
212: * lines will cause the system to emit a warning (TODO: hyphenate, if
213: * possible).
214: */
1.3 kristaps 215: void
216: flushln(struct termp *p)
217: {
1.25 kristaps 218: size_t i, j, vsz, vis, maxvis, mmax, bp;
1.3 kristaps 219:
220: /*
221: * First, establish the maximum columns of "visible" content.
222: * This is usually the difference between the right-margin and
223: * an indentation, but can be, for tagged lists or columns, a
224: * small set of values.
225: */
226:
227: assert(p->offset < p->rmargin);
228: maxvis = p->rmargin - p->offset;
1.25 kristaps 229: mmax = p->maxrmargin - p->offset;
230: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.3 kristaps 231: vis = 0;
232:
233: /*
234: * If in the standard case (left-justified), then begin with our
235: * indentation, otherwise (columns, etc.) just start spitting
236: * out text.
237: */
238:
239: if ( ! (p->flags & TERMP_NOLPAD))
240: /* LINTED */
241: for (j = 0; j < p->offset; j++)
242: putchar(' ');
243:
244: for (i = 0; i < p->col; i++) {
245: /*
246: * Count up visible word characters. Control sequences
1.23 kristaps 247: * (starting with the CSI) aren't counted. A space
248: * generates a non-printing word, which is valid (the
249: * space is printed according to regular spacing rules).
1.3 kristaps 250: */
251:
252: /* LINTED */
253: for (j = i, vsz = 0; j < p->col; j++) {
1.46 kristaps 254: if (' ' == p->buf[j])
1.3 kristaps 255: break;
1.45 kristaps 256: else if (8 == p->buf[j])
1.42 kristaps 257: j += 1;
1.45 kristaps 258: else
1.3 kristaps 259: vsz++;
260: }
261:
262: /*
1.25 kristaps 263: * Do line-breaking. If we're greater than our
264: * break-point and already in-line, break to the next
265: * line and start writing. If we're at the line start,
266: * then write out the word (TODO: hyphenate) and break
267: * in a subsequent loop invocation.
1.3 kristaps 268: */
269:
1.22 kristaps 270: if ( ! (TERMP_NOBREAK & p->flags)) {
1.25 kristaps 271: if (vis && vis + vsz > bp) {
1.22 kristaps 272: putchar('\n');
273: for (j = 0; j < p->offset; j++)
274: putchar(' ');
275: vis = 0;
1.25 kristaps 276: } else if (vis + vsz > bp)
277: warnx("word breaks right margin");
278:
279: /* TODO: hyphenate. */
280:
281: } else {
282: if (vis && vis + vsz > bp) {
283: putchar('\n');
284: for (j = 0; j < p->rmargin; j++)
285: putchar(' ');
1.30 kristaps 286: vis = p->rmargin - p->offset;
1.25 kristaps 287: } else if (vis + vsz > bp)
288: warnx("word breaks right margin");
289:
290: /* TODO: hyphenate. */
1.24 kristaps 291: }
1.3 kristaps 292:
293: /*
294: * Write out the word and a trailing space. Omit the
1.25 kristaps 295: * space if we're the last word in the line or beyond
296: * our breakpoint.
1.3 kristaps 297: */
298:
299: for ( ; i < p->col; i++) {
1.46 kristaps 300: if (' ' == p->buf[i])
1.3 kristaps 301: break;
302: putchar(p->buf[i]);
303: }
304: vis += vsz;
1.25 kristaps 305: if (i < p->col && vis <= bp) {
1.3 kristaps 306: putchar(' ');
307: vis++;
308: }
309: }
310:
1.25 kristaps 311: /*
312: * If we've overstepped our maximum visible no-break space, then
313: * cause a newline and offset at the right margin.
314: */
315:
1.22 kristaps 316: if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
1.32 kristaps 317: if ( ! (TERMP_NONOBREAK & p->flags)) {
318: putchar('\n');
319: for (i = 0; i < p->rmargin; i++)
320: putchar(' ');
321: }
1.22 kristaps 322: p->col = 0;
323: return;
324: }
325:
1.3 kristaps 326: /*
327: * If we're not to right-marginalise it (newline), then instead
328: * pad to the right margin and stay off.
329: */
330:
1.32 kristaps 331: if (p->flags & TERMP_NOBREAK) {
332: if ( ! (TERMP_NONOBREAK & p->flags))
333: for ( ; vis < maxvis; vis++)
334: putchar(' ');
335: } else
1.3 kristaps 336: putchar('\n');
337:
338: p->col = 0;
339: }
340:
341:
1.25 kristaps 342: /*
343: * A newline only breaks an existing line; it won't assert vertical
344: * space. All data in the output buffer is flushed prior to the newline
345: * assertion.
346: */
1.3 kristaps 347: void
348: newln(struct termp *p)
349: {
1.1 kristaps 350:
1.3 kristaps 351: p->flags |= TERMP_NOSPACE;
1.12 kristaps 352: if (0 == p->col) {
353: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 354: return;
1.12 kristaps 355: }
1.3 kristaps 356: flushln(p);
1.11 kristaps 357: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 358: }
359:
360:
1.25 kristaps 361: /*
362: * Asserts a vertical space (a full, empty line-break between lines).
363: * Note that if used twice, this will cause two blank spaces and so on.
364: * All data in the output buffer is flushed prior to the newline
365: * assertion.
366: */
1.3 kristaps 367: void
368: vspace(struct termp *p)
369: {
370:
371: newln(p);
372: putchar('\n');
373: }
374:
375:
1.25 kristaps 376: /*
377: * Break apart a word into "pwords" (partial-words, usually from
378: * breaking up a phrase into individual words) and, eventually, put them
379: * into the output buffer. If we're a literal word, then don't break up
380: * the word and put it verbatim into the output buffer.
381: */
1.3 kristaps 382: void
383: word(struct termp *p, const char *word)
384: {
385: size_t i, j, len;
386:
387: if (p->flags & TERMP_LITERAL) {
388: pword(p, word, strlen(word));
389: return;
390: }
391:
1.40 kristaps 392: if (0 == (len = strlen(word)))
393: errx(1, "blank line not in literal context");
1.3 kristaps 394:
395: if (mdoc_isdelim(word)) {
396: if ( ! (p->flags & TERMP_IGNDELIM))
397: p->flags |= TERMP_NOSPACE;
398: p->flags &= ~TERMP_IGNDELIM;
399: }
400:
401: /* LINTED */
402: for (j = i = 0; i < len; i++) {
1.46 kristaps 403: if (' ' != word[i]) {
1.3 kristaps 404: j++;
405: continue;
1.20 kristaps 406: }
407:
408: /* Escaped spaces don't delimit... */
1.46 kristaps 409: if (i && ' ' == word[i] && '\\' == word[i - 1]) {
1.20 kristaps 410: j++;
411: continue;
1.1 kristaps 412: }
1.20 kristaps 413:
1.3 kristaps 414: if (0 == j)
415: continue;
416: assert(i >= j);
417: pword(p, &word[i - j], j);
418: j = 0;
419: }
420: if (j > 0) {
421: assert(i >= j);
422: pword(p, &word[i - j], j);
423: }
424: }
425:
426:
1.25 kristaps 427: /*
428: * This is the main function for printing out nodes. It's constituted
429: * of PRE and POST functions, which correspond to prefix and infix
430: * processing. The termpair structure allows data to persist between
431: * prefix and postfix invocations.
432: */
1.3 kristaps 433: static void
1.12 kristaps 434: body(struct termp *p, struct termpair *ppair,
435: const struct mdoc_meta *meta,
1.3 kristaps 436: const struct mdoc_node *node)
437: {
438: int dochild;
1.9 kristaps 439: struct termpair pair;
1.3 kristaps 440:
1.38 kristaps 441: /* Some quick sanity-checking. */
442:
443: sanity(node);
444:
1.3 kristaps 445: /* Pre-processing. */
446:
447: dochild = 1;
1.12 kristaps 448: pair.ppair = ppair;
1.9 kristaps 449: pair.type = 0;
1.11 kristaps 450: pair.offset = pair.rmargin = 0;
1.10 kristaps 451: pair.flag = 0;
1.12 kristaps 452: pair.count = 0;
1.3 kristaps 453:
454: if (MDOC_TEXT != node->type) {
455: if (termacts[node->tok].pre)
1.9 kristaps 456: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 457: dochild = 0;
458: } else /* MDOC_TEXT == node->type */
1.35 kristaps 459: word(p, node->string);
1.3 kristaps 460:
461: /* Children. */
462:
1.10 kristaps 463: if (TERMPAIR_FLAG & pair.type)
464: p->flags |= pair.flag;
1.9 kristaps 465:
1.3 kristaps 466: if (dochild && node->child)
1.12 kristaps 467: body(p, &pair, meta, node->child);
1.3 kristaps 468:
1.10 kristaps 469: if (TERMPAIR_FLAG & pair.type)
470: p->flags &= ~pair.flag;
1.9 kristaps 471:
1.3 kristaps 472: /* Post-processing. */
473:
474: if (MDOC_TEXT != node->type)
475: if (termacts[node->tok].post)
1.9 kristaps 476: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 477:
478: /* Siblings. */
1.1 kristaps 479:
1.3 kristaps 480: if (node->next)
1.12 kristaps 481: body(p, ppair, meta, node->next);
1.3 kristaps 482: }
483:
484:
485: static void
486: footer(struct termp *p, const struct mdoc_meta *meta)
487: {
488: struct tm *tm;
489: char *buf, *os;
490:
491: if (NULL == (buf = malloc(p->rmargin)))
492: err(1, "malloc");
493: if (NULL == (os = malloc(p->rmargin)))
494: err(1, "malloc");
495:
496: tm = localtime(&meta->date);
497:
1.7 kristaps 498: #ifdef __OpenBSD__
499: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
500: #else
1.3 kristaps 501: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
502: #endif
503: err(1, "strftime");
504:
1.15 kristaps 505: (void)strlcpy(os, meta->os, p->rmargin);
1.3 kristaps 506:
1.16 kristaps 507: /*
508: * This is /slightly/ different from regular groff output
509: * because we don't have page numbers. Print the following:
510: *
511: * OS MDOCDATE
512: */
513:
1.15 kristaps 514: vspace(p);
1.3 kristaps 515:
1.15 kristaps 516: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
517: p->rmargin = p->maxrmargin - strlen(buf);
518: p->offset = 0;
1.3 kristaps 519:
1.15 kristaps 520: word(p, os);
521: flushln(p);
1.3 kristaps 522:
1.15 kristaps 523: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
524: p->offset = p->rmargin;
525: p->rmargin = p->maxrmargin;
526: p->flags &= ~TERMP_NOBREAK;
527:
528: word(p, buf);
529: flushln(p);
1.1 kristaps 530:
1.3 kristaps 531: free(buf);
532: free(os);
1.1 kristaps 533: }
534:
535:
1.3 kristaps 536: static void
537: header(struct termp *p, const struct mdoc_meta *meta)
538: {
1.34 kristaps 539: char *buf, *title, *bufp;
1.18 kristaps 540:
541: p->rmargin = p->maxrmargin;
542: p->offset = 0;
1.3 kristaps 543:
544: if (NULL == (buf = malloc(p->rmargin)))
545: err(1, "malloc");
546: if (NULL == (title = malloc(p->rmargin)))
547: err(1, "malloc");
548:
1.16 kristaps 549: /*
550: * The header is strange. It has three components, which are
551: * really two with the first duplicated. It goes like this:
552: *
553: * IDENTIFIER TITLE IDENTIFIER
554: *
555: * The IDENTIFIER is NAME(SECTION), which is the command-name
556: * (if given, or "unknown" if not) followed by the manual page
557: * section. These are given in `Dt'. The TITLE is a free-form
558: * string depending on the manual volume. If not specified, it
559: * switches on the manual section.
560: */
561:
1.34 kristaps 562: assert(meta->vol);
563: (void)strlcpy(buf, meta->vol, p->rmargin);
1.13 kristaps 564:
1.34 kristaps 565: if (meta->arch) {
566: (void)strlcat(buf, " (", p->rmargin);
567: (void)strlcat(buf, meta->arch, p->rmargin);
568: (void)strlcat(buf, ")", p->rmargin);
569: }
1.13 kristaps 570:
1.34 kristaps 571: (void)snprintf(title, p->rmargin, "%s(%d)",
572: meta->title, meta->msec);
1.13 kristaps 573:
1.16 kristaps 574: for (bufp = title; *bufp; bufp++)
1.33 kristaps 575: *bufp = toupper((u_char)*bufp);
1.16 kristaps 576:
1.13 kristaps 577: p->offset = 0;
578: p->rmargin = (p->maxrmargin - strlen(buf)) / 2;
1.15 kristaps 579: p->flags |= TERMP_NOBREAK | TERMP_NOSPACE;
1.3 kristaps 580:
1.13 kristaps 581: word(p, title);
582: flushln(p);
1.3 kristaps 583:
1.15 kristaps 584: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.13 kristaps 585: p->offset = p->rmargin;
1.15 kristaps 586: p->rmargin = p->maxrmargin - strlen(title);
1.3 kristaps 587:
1.13 kristaps 588: word(p, buf);
589: flushln(p);
1.3 kristaps 590:
1.13 kristaps 591: p->offset = p->rmargin;
592: p->rmargin = p->maxrmargin;
593: p->flags &= ~TERMP_NOBREAK;
1.15 kristaps 594: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
1.3 kristaps 595:
1.13 kristaps 596: word(p, title);
597: flushln(p);
1.3 kristaps 598:
1.13 kristaps 599: p->rmargin = p->maxrmargin;
600: p->offset = 0;
601: p->flags &= ~TERMP_NOSPACE;
1.3 kristaps 602:
603: free(title);
604: free(buf);
605: }
1.25 kristaps 606:
607:
608: /*
609: * Determine the symbol indicated by an escape sequences, that is, one
610: * starting with a backslash. Once done, we pass this value into the
611: * output buffer by way of the symbol table.
612: */
613: static void
614: nescape(struct termp *p, const char *word, size_t len)
615: {
1.46 kristaps 616: const char *rhs;
617: size_t sz;
1.25 kristaps 618:
1.46 kristaps 619: if (NULL == (rhs = a2ascii(p->symtab, word, len, &sz))) {
1.27 kristaps 620: warnx("unsupported %zu-byte escape sequence", len);
621: return;
1.25 kristaps 622: }
1.27 kristaps 623:
1.46 kristaps 624: stringa(p, rhs, sz);
1.25 kristaps 625: }
626:
627:
628: /*
629: * Handle an escape sequence: determine its length and pass it to the
630: * escape-symbol look table. Note that we assume mdoc(3) has validated
631: * the escape sequence (we assert upon badly-formed escape sequences).
632: */
633: static void
634: pescape(struct termp *p, const char *word, size_t *i, size_t len)
635: {
636: size_t j;
637:
1.36 kristaps 638: if (++(*i) >= len) {
639: warnx("ignoring bad escape sequence");
640: return;
641: }
1.25 kristaps 642:
643: if ('(' == word[*i]) {
644: (*i)++;
1.36 kristaps 645: if (*i + 1 >= len) {
646: warnx("ignoring bad escape sequence");
647: return;
648: }
1.25 kristaps 649: nescape(p, &word[*i], 2);
650: (*i)++;
651: return;
652:
653: } else if ('*' == word[*i]) {
654: (*i)++;
1.36 kristaps 655: if (*i >= len) {
656: warnx("ignoring bad escape sequence");
657: return;
658: }
1.25 kristaps 659: switch (word[*i]) {
660: case ('('):
661: (*i)++;
1.36 kristaps 662: if (*i + 1 >= len) {
663: warnx("ignoring bad escape sequence");
664: return;
665: }
1.25 kristaps 666: nescape(p, &word[*i], 2);
667: (*i)++;
668: return;
669: case ('['):
670: break;
671: default:
672: nescape(p, &word[*i], 1);
673: return;
674: }
675:
676: } else if ('[' != word[*i]) {
677: nescape(p, &word[*i], 1);
678: return;
679: }
680:
681: (*i)++;
682: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
683: /* Loop... */ ;
684:
1.36 kristaps 685: if (0 == word[*i]) {
686: warnx("ignoring bad escape sequence");
687: return;
688: }
1.25 kristaps 689: nescape(p, &word[*i - j], j);
690: }
691:
692:
693: /*
694: * Handle pwords, partial words, which may be either a single word or a
695: * phrase that cannot be broken down (such as a literal string). This
696: * handles word styling.
697: */
698: static void
699: pword(struct termp *p, const char *word, size_t len)
700: {
701: size_t i;
702:
703: if ( ! (TERMP_NOSPACE & p->flags) &&
704: ! (TERMP_LITERAL & p->flags))
705: chara(p, ' ');
706:
707: if ( ! (p->flags & TERMP_NONOSPACE))
708: p->flags &= ~TERMP_NOSPACE;
709:
710: /*
1.42 kristaps 711: * If ANSI (word-length styling), then apply our style now,
712: * before the word.
1.25 kristaps 713: */
714:
715: for (i = 0; i < len; i++) {
716: if ('\\' == word[i]) {
717: pescape(p, word, &i, len);
718: continue;
719: }
1.42 kristaps 720:
1.45 kristaps 721: if (TERMP_STYLE & p->flags) {
1.42 kristaps 722: if (TERMP_BOLD & p->flags) {
723: chara(p, word[i]);
724: chara(p, 8);
725: }
726: if (TERMP_UNDER & p->flags) {
727: chara(p, '_');
728: chara(p, 8);
729: }
730: }
731:
1.25 kristaps 732: chara(p, word[i]);
733: }
734: }
735:
736:
737: /*
738: * Like chara() but for arbitrary-length buffers. Resize the buffer by
739: * a factor of two (if the buffer is less than that) or the buffer's
740: * size.
741: */
742: static void
743: stringa(struct termp *p, const char *c, size_t sz)
744: {
745: size_t s;
746:
1.27 kristaps 747: if (0 == sz)
748: return;
749:
1.25 kristaps 750: s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
751:
752: assert(c);
753: if (p->col + sz >= p->maxcols) {
754: p->buf = realloc(p->buf, s);
755: if (NULL == p->buf)
756: err(1, "realloc");
757: p->maxcols = s;
758: }
759:
760: (void)memcpy(&p->buf[p->col], c, sz);
761: p->col += sz;
762: }
763:
764:
765: /*
766: * Insert a single character into the line-buffer. If the buffer's
767: * space is exceeded, then allocate more space by doubling the buffer
768: * size.
769: */
770: static void
771: chara(struct termp *p, char c)
772: {
773:
774: if (p->col + 1 >= p->maxcols) {
775: p->buf = realloc(p->buf, p->maxcols * 2);
776: if (NULL == p->buf)
777: err(1, "malloc");
778: p->maxcols *= 2;
779: }
780: p->buf[(p->col)++] = c;
781: }
1.38 kristaps 782:
783:
784: static void
785: sanity(const struct mdoc_node *n)
786: {
787:
788: switch (n->type) {
789: case (MDOC_TEXT):
790: if (n->child)
791: errx(1, "regular form violated (1)");
792: if (NULL == n->parent)
793: errx(1, "regular form violated (2)");
794: if (NULL == n->string)
795: errx(1, "regular form violated (3)");
796: switch (n->parent->type) {
797: case (MDOC_TEXT):
798: /* FALLTHROUGH */
799: case (MDOC_ROOT):
800: errx(1, "regular form violated (4)");
801: /* NOTREACHED */
802: default:
803: break;
804: }
805: break;
806: case (MDOC_ELEM):
807: if (NULL == n->parent)
808: errx(1, "regular form violated (5)");
809: switch (n->parent->type) {
810: case (MDOC_TAIL):
811: /* FALLTHROUGH */
812: case (MDOC_BODY):
813: /* FALLTHROUGH */
814: case (MDOC_HEAD):
815: break;
816: default:
817: errx(1, "regular form violated (6)");
818: /* NOTREACHED */
819: }
820: if (n->child) switch (n->child->type) {
821: case (MDOC_TEXT):
822: break;
823: default:
824: errx(1, "regular form violated (7(");
825: /* NOTREACHED */
826: }
827: break;
828: case (MDOC_HEAD):
829: /* FALLTHROUGH */
830: case (MDOC_BODY):
831: /* FALLTHROUGH */
832: case (MDOC_TAIL):
833: if (NULL == n->parent)
834: errx(1, "regular form violated (8)");
835: if (MDOC_BLOCK != n->parent->type)
836: errx(1, "regular form violated (9)");
837: if (n->child) switch (n->child->type) {
838: case (MDOC_BLOCK):
839: /* FALLTHROUGH */
840: case (MDOC_ELEM):
841: /* FALLTHROUGH */
842: case (MDOC_TEXT):
843: break;
844: default:
845: errx(1, "regular form violated (a)");
846: /* NOTREACHED */
847: }
848: break;
849: case (MDOC_BLOCK):
850: if (NULL == n->parent)
851: errx(1, "regular form violated (b)");
852: if (NULL == n->child)
853: errx(1, "regular form violated (c)");
854: switch (n->parent->type) {
855: case (MDOC_ROOT):
856: /* FALLTHROUGH */
857: case (MDOC_HEAD):
858: /* FALLTHROUGH */
859: case (MDOC_BODY):
860: /* FALLTHROUGH */
861: case (MDOC_TAIL):
862: break;
863: default:
864: errx(1, "regular form violated (d)");
865: /* NOTREACHED */
866: }
867: switch (n->child->type) {
868: case (MDOC_ROOT):
869: /* FALLTHROUGH */
870: case (MDOC_ELEM):
871: errx(1, "regular form violated (e)");
872: /* NOTREACHED */
873: default:
874: break;
875: }
876: break;
877: case (MDOC_ROOT):
878: if (n->parent)
879: errx(1, "regular form violated (f)");
880: if (NULL == n->child)
881: errx(1, "regular form violated (10)");
882: switch (n->child->type) {
883: case (MDOC_BLOCK):
884: break;
885: default:
886: errx(1, "regular form violated (11)");
887: /* NOTREACHED */
888: }
889: break;
890: }
891: }
892:
1.43 kristaps 893:
1.46 kristaps 894: __dead void
1.43 kristaps 895: punt(struct nroffopt *nroff, char *in)
896: {
897: char *args[32];
898: char arg0[32], argm[32];
899: int i;
900:
901: warnx("punting to nroff!");
902:
903: i = 0;
904:
905: (void)strlcpy(arg0, "nroff", 32);
906: args[i++] = arg0;
907:
908: if (nroff->fl_h)
909: args[i++] = "-h";
910: if (nroff->fl_i)
911: args[i++] = "-i";
912:
913: if (nroff->arg_m) {
914: (void)strlcpy(argm, "-m", 32);
915: (void)strlcat(argm, nroff->arg_m, 32);
916: args[i++] = argm;
917: } else
918: args[i++] = "-mandoc";
919:
920: args[i++] = in;
921: args[i++] = (char *)NULL;
922:
923: (void)execvp("nroff", args);
924: errx(1, "exec");
925: /* NOTREACHED */
926: }
927:
CVSweb