Annotation of mandoc/mdocterm.c, Revision 1.26
1.26 ! kristaps 1: /* $Id: mdocterm.c,v 1.25 2009/03/02 17:14:46 kristaps Exp $ */
1.1 kristaps 2: /*
3: * Copyright (c) 2008 Kristaps Dzonsons <kristaps@kth.se>
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.18 kristaps 19: #include <sys/utsname.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.7 kristaps 28: #ifndef __OpenBSD__
1.5 kristaps 29: #include <time.h>
30: #endif
31:
1.2 kristaps 32: #include "mmain.h"
1.1 kristaps 33: #include "term.h"
34:
1.3 kristaps 35: static void body(struct termp *,
1.12 kristaps 36: struct termpair *,
1.3 kristaps 37: const struct mdoc_meta *,
38: const struct mdoc_node *);
39: static void header(struct termp *,
40: const struct mdoc_meta *);
41: static void footer(struct termp *,
42: const struct mdoc_meta *);
43:
44: static void pword(struct termp *, const char *, size_t);
1.25 kristaps 45: static void pescape(struct termp *, const char *,
46: size_t *, size_t);
47: static void nescape(struct termp *,
1.12 kristaps 48: const char *, size_t);
1.3 kristaps 49: static void chara(struct termp *, char);
1.25 kristaps 50: static void stringa(struct termp *,
51: const char *, size_t);
52: static void symbola(struct termp *, enum tsym);
1.26 ! kristaps 53: static void stylea(struct termp *, enum tstyle);
1.3 kristaps 54:
55: #ifdef __linux__
56: extern size_t strlcat(char *, const char *, size_t);
57: extern size_t strlcpy(char *, const char *, size_t);
58: #endif
59:
1.25 kristaps 60: static struct termsym termsym_ansi[] = {
61: { "]", 1 }, /* TERMSYM_RBRACK */
62: { "[", 1 }, /* TERMSYM_LBRACK */
63: { "<-", 2 }, /* TERMSYM_LARROW */
64: { "->", 2 }, /* TERMSYM_RARROW */
65: { "^", 1 }, /* TERMSYM_UARROW */
66: { "v", 1 }, /* TERMSYM_DARROW */
67: { "`", 1 }, /* TERMSYM_LSQUOTE */
68: { "\'", 1 }, /* TERMSYM_RSQUOTE */
69: { "\'", 1 }, /* TERMSYM_SQUOTE */
70: { "``", 2 }, /* TERMSYM_LDQUOTE */
71: { "\'\'", 2 }, /* TERMSYM_RDQUOTE */
72: { "\"", 1 }, /* TERMSYM_DQUOTE */
73: { "<", 1 }, /* TERMSYM_LT */
74: { ">", 1 }, /* TERMSYM_GT */
75: { "<=", 2 }, /* TERMSYM_LE */
76: { ">=", 2 }, /* TERMSYM_GE */
77: { "==", 2 }, /* TERMSYM_EQ */
78: { "!=", 2 }, /* TERMSYM_NEQ */
79: { "\'", 1 }, /* TERMSYM_ACUTE */
80: { "`", 1 }, /* TERMSYM_GRAVE */
81: { "pi", 2 }, /* TERMSYM_PI */
82: { "+=", 2 }, /* TERMSYM_PLUSMINUS */
83: { "oo", 2 }, /* TERMSYM_INF */
84: { "infinity", 8 }, /* TERMSYM_INF2 */
85: { "NaN", 3 }, /* TERMSYM_NAN */
86: { "|", 1 }, /* TERMSYM_BAR */
87: { "o", 1 }, /* TERMSYM_BULLET */
88: { "&", 1 }, /* TERMSYM_AND */
89: { "|", 1 }, /* TERMSYM_OR */
90: };
91:
92: static const char ansi_clear[] = { 27, '[', '0', 'm' };
93: static const char ansi_bold[] = { 27, '[', '1', 'm' };
94: static const char ansi_under[] = { 27, '[', '4', 'm' };
95:
96: static struct termsym termstyle_ansi[] = {
97: { ansi_clear, 4 },
98: { ansi_bold, 4 },
99: { ansi_under, 4 }
100: };
101:
1.3 kristaps 102:
1.1 kristaps 103: int
104: main(int argc, char *argv[])
105: {
1.2 kristaps 106: struct mmain *p;
107: const struct mdoc *mdoc;
1.3 kristaps 108: struct termp termp;
1.2 kristaps 109:
110: p = mmain_alloc();
1.1 kristaps 111:
1.3 kristaps 112: if ( ! mmain_getopt(p, argc, argv, NULL, NULL, NULL, NULL))
1.2 kristaps 113: mmain_exit(p, 1);
1.1 kristaps 114:
1.3 kristaps 115: if (NULL == (mdoc = mmain_mdoc(p)))
116: mmain_exit(p, 1);
117:
1.19 kristaps 118: termp.maxrmargin = 78; /* XXX */
1.3 kristaps 119: termp.rmargin = termp.maxrmargin;
120: termp.maxcols = 1024;
121: termp.offset = termp.col = 0;
122: termp.flags = TERMP_NOSPACE;
1.25 kristaps 123: termp.symtab = termsym_ansi;
124: termp.styletab = termstyle_ansi;
1.3 kristaps 125:
126: if (NULL == (termp.buf = malloc(termp.maxcols)))
127: err(1, "malloc");
128:
1.15 kristaps 129: header(&termp, mdoc_meta(mdoc));
1.12 kristaps 130: body(&termp, NULL, mdoc_meta(mdoc), mdoc_node(mdoc));
1.3 kristaps 131: footer(&termp, mdoc_meta(mdoc));
132:
133: free(termp.buf);
134:
135: mmain_exit(p, 0);
136: /* NOTREACHED */
137: }
138:
139:
1.25 kristaps 140: /*
141: * Flush a line of text. A "line" is loosely defined as being something
142: * that should be followed by a newline, regardless of whether it's
143: * broken apart by newlines getting there. A line can also be a
144: * fragment of a columnar list.
145: *
146: * Specifically, a line is whatever's in p->buf of length p->col, which
147: * is zeroed after this function returns.
148: *
149: * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
150: * critical importance here. Their behaviour follows:
151: *
152: * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
153: * offset value. This is useful when doing columnar lists where the
154: * prior column has right-padded.
155: *
156: * - TERMP_LITERAL: don't break apart words. Note that a long literal
157: * word will violate the right margin.
158: *
159: * - TERMP_NOBREAK: this is the most important and is used when making
160: * columns. In short: don't print a newline and instead pad to the
161: * right margin. Used in conjunction with TERMP_NOLPAD.
162: *
163: * In-line line breaking:
164: *
165: * If TERMP_NOBREAK is specified and the line overruns the right
166: * margin, it will break and pad-right to the right margin after
167: * writing. If maxrmargin is violated, it will break and continue
168: * writing from the right-margin, which will lead to the above
169: * scenario upon exit.
170: *
171: * Otherwise, the line will break at the right margin. Extremely long
172: * lines will cause the system to emit a warning (TODO: hyphenate, if
173: * possible).
174: */
1.3 kristaps 175: void
176: flushln(struct termp *p)
177: {
1.25 kristaps 178: size_t i, j, vsz, vis, maxvis, mmax, bp;
1.3 kristaps 179:
180: /*
181: * First, establish the maximum columns of "visible" content.
182: * This is usually the difference between the right-margin and
183: * an indentation, but can be, for tagged lists or columns, a
184: * small set of values.
185: */
186:
187: assert(p->offset < p->rmargin);
188: maxvis = p->rmargin - p->offset;
1.25 kristaps 189: mmax = p->maxrmargin - p->offset;
190: bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
1.3 kristaps 191: vis = 0;
192:
193: /*
194: * If in the standard case (left-justified), then begin with our
195: * indentation, otherwise (columns, etc.) just start spitting
196: * out text.
197: */
198:
199: if ( ! (p->flags & TERMP_NOLPAD))
200: /* LINTED */
201: for (j = 0; j < p->offset; j++)
202: putchar(' ');
203:
204: for (i = 0; i < p->col; i++) {
205: /*
206: * Count up visible word characters. Control sequences
1.23 kristaps 207: * (starting with the CSI) aren't counted. A space
208: * generates a non-printing word, which is valid (the
209: * space is printed according to regular spacing rules).
1.3 kristaps 210: */
211:
1.25 kristaps 212: /* FIXME: make non-ANSI friendly. */
213:
1.3 kristaps 214: /* LINTED */
215: for (j = i, vsz = 0; j < p->col; j++) {
1.25 kristaps 216: if (isspace((int)p->buf[j]))
1.3 kristaps 217: break;
218: else if (27 == p->buf[j]) {
219: assert(j + 4 <= p->col);
220: j += 3;
221: } else
222: vsz++;
223: }
224:
225: /*
1.25 kristaps 226: * Do line-breaking. If we're greater than our
227: * break-point and already in-line, break to the next
228: * line and start writing. If we're at the line start,
229: * then write out the word (TODO: hyphenate) and break
230: * in a subsequent loop invocation.
1.3 kristaps 231: */
232:
1.22 kristaps 233: if ( ! (TERMP_NOBREAK & p->flags)) {
1.25 kristaps 234: if (vis && vis + vsz > bp) {
1.22 kristaps 235: putchar('\n');
236: for (j = 0; j < p->offset; j++)
237: putchar(' ');
238: vis = 0;
1.25 kristaps 239: } else if (vis + vsz > bp)
240: warnx("word breaks right margin");
241:
242: /* TODO: hyphenate. */
243:
244: } else {
245: if (vis && vis + vsz > bp) {
246: putchar('\n');
247: for (j = 0; j < p->rmargin; j++)
248: putchar(' ');
249: vis = p->rmargin;
250: } else if (vis + vsz > bp)
251: warnx("word breaks right margin");
252:
253: /* TODO: hyphenate. */
1.24 kristaps 254: }
1.3 kristaps 255:
256: /*
257: * Write out the word and a trailing space. Omit the
1.25 kristaps 258: * space if we're the last word in the line or beyond
259: * our breakpoint.
1.3 kristaps 260: */
261:
262: for ( ; i < p->col; i++) {
1.25 kristaps 263: if (isspace((int)p->buf[i]))
1.3 kristaps 264: break;
265: putchar(p->buf[i]);
266: }
267: vis += vsz;
1.25 kristaps 268: if (i < p->col && vis <= bp) {
1.3 kristaps 269: putchar(' ');
270: vis++;
271: }
272: }
273:
1.25 kristaps 274: /*
275: * If we've overstepped our maximum visible no-break space, then
276: * cause a newline and offset at the right margin.
277: */
278:
1.22 kristaps 279: if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
280: putchar('\n');
281: for (i = 0; i < p->rmargin; i++)
282: putchar(' ');
283: p->col = 0;
284: return;
285: }
286:
1.3 kristaps 287: /*
288: * If we're not to right-marginalise it (newline), then instead
289: * pad to the right margin and stay off.
290: */
291:
292: if (p->flags & TERMP_NOBREAK) {
1.22 kristaps 293: for ( ; vis < maxvis; vis++)
294: putchar(' ');
1.3 kristaps 295: } else
296: putchar('\n');
297:
298: p->col = 0;
299: }
300:
301:
1.25 kristaps 302: /*
303: * A newline only breaks an existing line; it won't assert vertical
304: * space. All data in the output buffer is flushed prior to the newline
305: * assertion.
306: */
1.3 kristaps 307: void
308: newln(struct termp *p)
309: {
1.1 kristaps 310:
1.3 kristaps 311: p->flags |= TERMP_NOSPACE;
1.12 kristaps 312: if (0 == p->col) {
313: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 314: return;
1.12 kristaps 315: }
1.3 kristaps 316: flushln(p);
1.11 kristaps 317: p->flags &= ~TERMP_NOLPAD;
1.3 kristaps 318: }
319:
320:
1.25 kristaps 321: /*
322: * Asserts a vertical space (a full, empty line-break between lines).
323: * Note that if used twice, this will cause two blank spaces and so on.
324: * All data in the output buffer is flushed prior to the newline
325: * assertion.
326: */
1.3 kristaps 327: void
328: vspace(struct termp *p)
329: {
330:
331: newln(p);
332: putchar('\n');
333: }
334:
335:
1.25 kristaps 336: /*
337: * Break apart a word into "pwords" (partial-words, usually from
338: * breaking up a phrase into individual words) and, eventually, put them
339: * into the output buffer. If we're a literal word, then don't break up
340: * the word and put it verbatim into the output buffer.
341: */
1.3 kristaps 342: void
343: word(struct termp *p, const char *word)
344: {
345: size_t i, j, len;
346:
347: if (p->flags & TERMP_LITERAL) {
348: pword(p, word, strlen(word));
349: return;
350: }
351:
352: len = strlen(word);
353: assert(len > 0);
354:
355: if (mdoc_isdelim(word)) {
356: if ( ! (p->flags & TERMP_IGNDELIM))
357: p->flags |= TERMP_NOSPACE;
358: p->flags &= ~TERMP_IGNDELIM;
359: }
360:
361: /* LINTED */
362: for (j = i = 0; i < len; i++) {
1.25 kristaps 363: if ( ! isspace((int)word[i])) {
1.3 kristaps 364: j++;
365: continue;
1.20 kristaps 366: }
367:
368: /* Escaped spaces don't delimit... */
1.25 kristaps 369: if (i > 0 && isspace((int)word[i]) &&
370: '\\' == word[i - 1]) {
1.20 kristaps 371: j++;
372: continue;
1.1 kristaps 373: }
1.20 kristaps 374:
1.3 kristaps 375: if (0 == j)
376: continue;
377: assert(i >= j);
378: pword(p, &word[i - j], j);
379: j = 0;
380: }
381: if (j > 0) {
382: assert(i >= j);
383: pword(p, &word[i - j], j);
384: }
385: }
386:
387:
1.25 kristaps 388: /*
389: * This is the main function for printing out nodes. It's constituted
390: * of PRE and POST functions, which correspond to prefix and infix
391: * processing. The termpair structure allows data to persist between
392: * prefix and postfix invocations.
393: */
1.3 kristaps 394: static void
1.12 kristaps 395: body(struct termp *p, struct termpair *ppair,
396: const struct mdoc_meta *meta,
1.3 kristaps 397: const struct mdoc_node *node)
398: {
399: int dochild;
1.9 kristaps 400: struct termpair pair;
1.3 kristaps 401:
402: /* Pre-processing. */
403:
404: dochild = 1;
1.12 kristaps 405: pair.ppair = ppair;
1.9 kristaps 406: pair.type = 0;
1.11 kristaps 407: pair.offset = pair.rmargin = 0;
1.10 kristaps 408: pair.flag = 0;
1.12 kristaps 409: pair.count = 0;
1.3 kristaps 410:
411: if (MDOC_TEXT != node->type) {
412: if (termacts[node->tok].pre)
1.9 kristaps 413: if ( ! (*termacts[node->tok].pre)(p, &pair, meta, node))
1.3 kristaps 414: dochild = 0;
415: } else /* MDOC_TEXT == node->type */
416: word(p, node->data.text.string);
417:
418: /* Children. */
419:
1.10 kristaps 420: if (TERMPAIR_FLAG & pair.type)
421: p->flags |= pair.flag;
1.9 kristaps 422:
1.3 kristaps 423: if (dochild && node->child)
1.12 kristaps 424: body(p, &pair, meta, node->child);
1.3 kristaps 425:
1.10 kristaps 426: if (TERMPAIR_FLAG & pair.type)
427: p->flags &= ~pair.flag;
1.9 kristaps 428:
1.3 kristaps 429: /* Post-processing. */
430:
431: if (MDOC_TEXT != node->type)
432: if (termacts[node->tok].post)
1.9 kristaps 433: (*termacts[node->tok].post)(p, &pair, meta, node);
1.3 kristaps 434:
435: /* Siblings. */
1.1 kristaps 436:
1.3 kristaps 437: if (node->next)
1.12 kristaps 438: body(p, ppair, meta, node->next);
1.3 kristaps 439: }
440:
441:
442: static void
443: footer(struct termp *p, const struct mdoc_meta *meta)
444: {
445: struct tm *tm;
446: char *buf, *os;
447:
448: if (NULL == (buf = malloc(p->rmargin)))
449: err(1, "malloc");
450: if (NULL == (os = malloc(p->rmargin)))
451: err(1, "malloc");
452:
453: tm = localtime(&meta->date);
454:
1.7 kristaps 455: #ifdef __OpenBSD__
456: if (NULL == strftime(buf, p->rmargin, "%B %d, %Y", tm))
457: #else
1.3 kristaps 458: if (0 == strftime(buf, p->rmargin, "%B %d, %Y", tm))
459: #endif
460: err(1, "strftime");
461:
1.15 kristaps 462: (void)strlcpy(os, meta->os, p->rmargin);
1.3 kristaps 463:
1.16 kristaps 464: /*
465: * This is /slightly/ different from regular groff output
466: * because we don't have page numbers. Print the following:
467: *
468: * OS MDOCDATE
469: */
470:
1.15 kristaps 471: vspace(p);
1.3 kristaps 472:
1.15 kristaps 473: p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
474: p->rmargin = p->maxrmargin - strlen(buf);
475: p->offset = 0;
1.3 kristaps 476:
1.15 kristaps 477: word(p, os);
478: flushln(p);
1.3 kristaps 479:
1.15 kristaps 480: p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
481: p->offset = p->rmargin;
482: p->rmargin = p->maxrmargin;
483: p->flags &= ~TERMP_NOBREAK;
484:
485: word(p, buf);
486: flushln(p);
1.1 kristaps 487:
1.3 kristaps 488: free(buf);
489: free(os);
1.1 kristaps 490: }
491:
492:
1.3 kristaps 493: static void
494: header(struct termp *p, const struct mdoc_meta *meta)
495: {
1.18 kristaps 496: char *buf, *title, *bufp, *vbuf;
1.13 kristaps 497: const char *pp;
1.18 kristaps 498: struct utsname uts;
499:
500: p->rmargin = p->maxrmargin;
501: p->offset = 0;
1.3 kristaps 502:
503: if (NULL == (buf = malloc(p->rmargin)))
504: err(1, "malloc");
505: if (NULL == (title = malloc(p->rmargin)))
506: err(1, "malloc");
1.18 kristaps 507: if (NULL == (vbuf = malloc(p->rmargin)))
508: err(1, "malloc");
1.3 kristaps 509:
1.18 kristaps 510: if (NULL == (pp = mdoc_vol2a(meta->vol))) {
1.3 kristaps 511: switch (meta->msec) {
512: case (MSEC_1):
513: /* FALLTHROUGH */
514: case (MSEC_6):
515: /* FALLTHROUGH */
516: case (MSEC_7):
517: pp = mdoc_vol2a(VOL_URM);
518: break;
519: case (MSEC_8):
520: pp = mdoc_vol2a(VOL_SMM);
521: break;
522: case (MSEC_2):
523: /* FALLTHROUGH */
524: case (MSEC_3):
525: /* FALLTHROUGH */
526: case (MSEC_4):
527: /* FALLTHROUGH */
528: case (MSEC_5):
529: pp = mdoc_vol2a(VOL_PRM);
530: break;
531: case (MSEC_9):
532: pp = mdoc_vol2a(VOL_KM);
533: break;
534: default:
535: break;
536: }
1.18 kristaps 537: }
538: vbuf[0] = 0;
539:
540: if (pp) {
541: if (-1 == uname(&uts))
542: err(1, "uname");
543: (void)strlcat(vbuf, uts.sysname, p->rmargin);
544: (void)strlcat(vbuf, " ", p->rmargin);
545: } else if (NULL == (pp = mdoc_msec2a(meta->msec)))
546: pp = mdoc_msec2a(MSEC_local);
547:
548: (void)strlcat(vbuf, pp, p->rmargin);
1.3 kristaps 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.13 kristaps 563: if (mdoc_arch2a(meta->arch))
1.16 kristaps 564: (void)snprintf(buf, p->rmargin, "%s (%s)",
1.18 kristaps 565: vbuf, mdoc_arch2a(meta->arch));
1.13 kristaps 566: else
1.18 kristaps 567: (void)strlcpy(buf, vbuf, p->rmargin);
1.13 kristaps 568:
569: pp = mdoc_msec2a(meta->msec);
570:
571: (void)snprintf(title, p->rmargin, "%s(%s)",
572: meta->title, pp ? pp : "");
573:
1.16 kristaps 574: for (bufp = title; *bufp; bufp++)
575: *bufp = toupper(*bufp);
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);
1.18 kristaps 604: free(vbuf);
1.3 kristaps 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: {
617:
618: switch (len) {
619: case (1):
620: switch (word[0]) {
621: case ('\\'):
622: /* FALLTHROUGH */
623: case ('\''):
624: /* FALLTHROUGH */
625: case ('`'):
626: /* FALLTHROUGH */
627: case ('-'):
628: /* FALLTHROUGH */
629: case (' '):
630: /* FALLTHROUGH */
631: case ('.'):
632: chara(p, word[0]); /* FIXME */
633: break;
634: case ('&'):
635: break;
636: case ('e'):
637: chara(p, '\\'); /* FIXME */
638: break;
639: case ('q'):
640: symbola(p, TERMSYM_DQUOTE);
641: break;
642: default:
643: warnx("escape sequence not supported: %c",
644: word[0]);
645: break;
646: }
647: break;
648:
649: case (2):
650: if ('r' == word[0] && 'B' == word[1])
651: symbola(p, TERMSYM_RBRACK);
652: else if ('l' == word[0] && 'B' == word[1])
653: symbola(p, TERMSYM_LBRACK);
654: else if ('l' == word[0] && 'q' == word[1])
655: symbola(p, TERMSYM_LDQUOTE);
656: else if ('r' == word[0] && 'q' == word[1])
657: symbola(p, TERMSYM_RDQUOTE);
658: else if ('o' == word[0] && 'q' == word[1])
659: symbola(p, TERMSYM_LSQUOTE);
660: else if ('a' == word[0] && 'q' == word[1])
661: symbola(p, TERMSYM_RSQUOTE);
662: else if ('<' == word[0] && '-' == word[1])
663: symbola(p, TERMSYM_LARROW);
664: else if ('-' == word[0] && '>' == word[1])
665: symbola(p, TERMSYM_RARROW);
666: else if ('b' == word[0] && 'u' == word[1])
667: symbola(p, TERMSYM_BULLET);
668: else if ('<' == word[0] && '=' == word[1])
669: symbola(p, TERMSYM_LE);
670: else if ('>' == word[0] && '=' == word[1])
671: symbola(p, TERMSYM_GE);
672: else if ('=' == word[0] && '=' == word[1])
673: symbola(p, TERMSYM_EQ);
674: else if ('+' == word[0] && '-' == word[1])
675: symbola(p, TERMSYM_PLUSMINUS);
676: else if ('u' == word[0] && 'a' == word[1])
677: symbola(p, TERMSYM_UARROW);
678: else if ('d' == word[0] && 'a' == word[1])
679: symbola(p, TERMSYM_DARROW);
680: else if ('a' == word[0] && 'a' == word[1])
681: symbola(p, TERMSYM_ACUTE);
682: else if ('g' == word[0] && 'a' == word[1])
683: symbola(p, TERMSYM_GRAVE);
684: else if ('!' == word[0] && '=' == word[1])
685: symbola(p, TERMSYM_NEQ);
686: else if ('i' == word[0] && 'f' == word[1])
687: symbola(p, TERMSYM_INF);
688: else if ('n' == word[0] && 'a' == word[1])
689: symbola(p, TERMSYM_NAN);
690: else if ('b' == word[0] && 'a' == word[1])
691: symbola(p, TERMSYM_BAR);
692:
693: /* Deprecated forms. */
694: else if ('A' == word[0] && 'm' == word[1])
695: symbola(p, TERMSYM_AMP);
696: else if ('B' == word[0] && 'a' == word[1])
697: symbola(p, TERMSYM_BAR);
698: else if ('I' == word[0] && 'f' == word[1])
699: symbola(p, TERMSYM_INF2);
700: else if ('G' == word[0] && 'e' == word[1])
701: symbola(p, TERMSYM_GE);
702: else if ('G' == word[0] && 't' == word[1])
703: symbola(p, TERMSYM_GT);
704: else if ('L' == word[0] && 'e' == word[1])
705: symbola(p, TERMSYM_LE);
706: else if ('L' == word[0] && 'q' == word[1])
707: symbola(p, TERMSYM_LDQUOTE);
708: else if ('L' == word[0] && 't' == word[1])
709: symbola(p, TERMSYM_LT);
710: else if ('N' == word[0] && 'a' == word[1])
711: symbola(p, TERMSYM_NAN);
712: else if ('N' == word[0] && 'e' == word[1])
713: symbola(p, TERMSYM_NEQ);
714: else if ('P' == word[0] && 'i' == word[1])
715: symbola(p, TERMSYM_PI);
716: else if ('P' == word[0] && 'm' == word[1])
717: symbola(p, TERMSYM_PLUSMINUS);
718: else if ('R' == word[0] && 'q' == word[1])
719: symbola(p, TERMSYM_RDQUOTE);
720: else
721: warnx("escape sequence not supported: %c%c",
722: word[0], word[1]);
723: break;
724:
725: default:
726: warnx("escape sequence not supported");
727: break;
728: }
729: }
730:
731:
732: /*
733: * Handle an escape sequence: determine its length and pass it to the
734: * escape-symbol look table. Note that we assume mdoc(3) has validated
735: * the escape sequence (we assert upon badly-formed escape sequences).
736: */
737: static void
738: pescape(struct termp *p, const char *word, size_t *i, size_t len)
739: {
740: size_t j;
741:
742: (*i)++;
743: assert(*i < len);
744:
745: if ('(' == word[*i]) {
746: (*i)++;
747: assert(*i + 1 < len);
748: nescape(p, &word[*i], 2);
749: (*i)++;
750: return;
751:
752: } else if ('*' == word[*i]) {
753: /* XXX - deprecated! */
754: (*i)++;
755: assert(*i < len);
756: switch (word[*i]) {
757: case ('('):
758: (*i)++;
759: assert(*i + 1 < len);
760: nescape(p, &word[*i], 2);
761: (*i)++;
762: return;
763: case ('['):
764: break;
765: default:
766: nescape(p, &word[*i], 1);
767: return;
768: }
769:
770: } else if ('[' != word[*i]) {
771: nescape(p, &word[*i], 1);
772: return;
773: }
774:
775: (*i)++;
776: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
777: /* Loop... */ ;
778:
779: assert(word[*i]);
780: nescape(p, &word[*i - j], j);
781: }
782:
783:
784: /*
785: * Handle pwords, partial words, which may be either a single word or a
786: * phrase that cannot be broken down (such as a literal string). This
787: * handles word styling.
788: */
789: static void
790: pword(struct termp *p, const char *word, size_t len)
791: {
792: size_t i;
793:
794: if ( ! (TERMP_NOSPACE & p->flags) &&
795: ! (TERMP_LITERAL & p->flags))
796: chara(p, ' ');
797:
798: if ( ! (p->flags & TERMP_NONOSPACE))
799: p->flags &= ~TERMP_NOSPACE;
800:
801: /*
802: * XXX - if literal and underlining, this will underline the
803: * spaces between literal words.
804: */
805:
806: if (p->flags & TERMP_BOLD)
1.26 ! kristaps 807: stylea(p, TERMSTYLE_BOLD);
1.25 kristaps 808: if (p->flags & TERMP_UNDERLINE)
1.26 ! kristaps 809: stylea(p, TERMSTYLE_UNDER);
1.25 kristaps 810:
811: for (i = 0; i < len; i++) {
812: if ('\\' == word[i]) {
813: pescape(p, word, &i, len);
814: continue;
815: }
816: chara(p, word[i]);
817: }
818:
819: if (p->flags & TERMP_BOLD ||
820: p->flags & TERMP_UNDERLINE)
1.26 ! kristaps 821: stylea(p, TERMSTYLE_CLEAR);
1.25 kristaps 822: }
823:
824:
825: /*
826: * Add a symbol to the output line buffer.
827: */
828: static void
829: symbola(struct termp *p, enum tsym sym)
830: {
831:
832: assert(p->symtab[sym].sym);
833: stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
834: }
835:
836:
837: /*
1.26 ! kristaps 838: * Add a style to the output line buffer.
! 839: */
! 840: static void
! 841: stylea(struct termp *p, enum tstyle style)
! 842: {
! 843:
! 844: assert(p->styletab[style].sym);
! 845: stringa(p, p->styletab[style].sym, p->styletab[style].sz);
! 846: }
! 847:
! 848:
! 849: /*
1.25 kristaps 850: * Like chara() but for arbitrary-length buffers. Resize the buffer by
851: * a factor of two (if the buffer is less than that) or the buffer's
852: * size.
853: */
854: static void
855: stringa(struct termp *p, const char *c, size_t sz)
856: {
857: size_t s;
858:
859: s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
860:
861: assert(c);
862: if (p->col + sz >= p->maxcols) {
863: p->buf = realloc(p->buf, s);
864: if (NULL == p->buf)
865: err(1, "realloc");
866: p->maxcols = s;
867: }
868:
869: (void)memcpy(&p->buf[p->col], c, sz);
870: p->col += sz;
871: }
872:
873:
874: /*
875: * Insert a single character into the line-buffer. If the buffer's
876: * space is exceeded, then allocate more space by doubling the buffer
877: * size.
878: */
879: static void
880: chara(struct termp *p, char c)
881: {
882:
883: if (p->col + 1 >= p->maxcols) {
884: p->buf = realloc(p->buf, p->maxcols * 2);
885: if (NULL == p->buf)
886: err(1, "malloc");
887: p->maxcols *= 2;
888: }
889: p->buf[(p->col)++] = c;
890: }
CVSweb