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