Annotation of mandoc/mdocterm.c, Revision 1.25
1.25 ! kristaps 1: /* $Id: mdocterm.c,v 1.24 2009/03/02 12:09:32 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 style(struct termp *, enum tstyle);
! 48: static void nescape(struct termp *,
1.12 kristaps 49: const char *, size_t);
1.3 kristaps 50: static void chara(struct termp *, char);
1.25 ! kristaps 51: static void stringa(struct termp *,
! 52: const char *, size_t);
! 53: static void symbola(struct termp *, enum tsym);
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: * Apply a style to the output buffer. This is looked up by means of
! 734: * the styletab.
! 735: */
! 736: static void
! 737: style(struct termp *p, enum tstyle esc)
! 738: {
! 739:
! 740: if (p->col + 4 >= p->maxcols)
! 741: errx(1, "line overrun");
! 742:
! 743: p->buf[(p->col)++] = 27;
! 744: p->buf[(p->col)++] = '[';
! 745: switch (esc) {
! 746: case (TERMSTYLE_CLEAR):
! 747: p->buf[(p->col)++] = '0';
! 748: break;
! 749: case (TERMSTYLE_BOLD):
! 750: p->buf[(p->col)++] = '1';
! 751: break;
! 752: case (TERMSTYLE_UNDER):
! 753: p->buf[(p->col)++] = '4';
! 754: break;
! 755: default:
! 756: abort();
! 757: /* NOTREACHED */
! 758: }
! 759: p->buf[(p->col)++] = 'm';
! 760: }
! 761:
! 762:
! 763: /*
! 764: * Handle an escape sequence: determine its length and pass it to the
! 765: * escape-symbol look table. Note that we assume mdoc(3) has validated
! 766: * the escape sequence (we assert upon badly-formed escape sequences).
! 767: */
! 768: static void
! 769: pescape(struct termp *p, const char *word, size_t *i, size_t len)
! 770: {
! 771: size_t j;
! 772:
! 773: (*i)++;
! 774: assert(*i < len);
! 775:
! 776: if ('(' == word[*i]) {
! 777: (*i)++;
! 778: assert(*i + 1 < len);
! 779: nescape(p, &word[*i], 2);
! 780: (*i)++;
! 781: return;
! 782:
! 783: } else if ('*' == word[*i]) {
! 784: /* XXX - deprecated! */
! 785: (*i)++;
! 786: assert(*i < len);
! 787: switch (word[*i]) {
! 788: case ('('):
! 789: (*i)++;
! 790: assert(*i + 1 < len);
! 791: nescape(p, &word[*i], 2);
! 792: (*i)++;
! 793: return;
! 794: case ('['):
! 795: break;
! 796: default:
! 797: nescape(p, &word[*i], 1);
! 798: return;
! 799: }
! 800:
! 801: } else if ('[' != word[*i]) {
! 802: nescape(p, &word[*i], 1);
! 803: return;
! 804: }
! 805:
! 806: (*i)++;
! 807: for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
! 808: /* Loop... */ ;
! 809:
! 810: assert(word[*i]);
! 811: nescape(p, &word[*i - j], j);
! 812: }
! 813:
! 814:
! 815: /*
! 816: * Handle pwords, partial words, which may be either a single word or a
! 817: * phrase that cannot be broken down (such as a literal string). This
! 818: * handles word styling.
! 819: */
! 820: static void
! 821: pword(struct termp *p, const char *word, size_t len)
! 822: {
! 823: size_t i;
! 824:
! 825: if ( ! (TERMP_NOSPACE & p->flags) &&
! 826: ! (TERMP_LITERAL & p->flags))
! 827: chara(p, ' ');
! 828:
! 829: if ( ! (p->flags & TERMP_NONOSPACE))
! 830: p->flags &= ~TERMP_NOSPACE;
! 831:
! 832: /*
! 833: * XXX - if literal and underlining, this will underline the
! 834: * spaces between literal words.
! 835: */
! 836:
! 837: if (p->flags & TERMP_BOLD)
! 838: style(p, TERMSTYLE_BOLD);
! 839: if (p->flags & TERMP_UNDERLINE)
! 840: style(p, TERMSTYLE_UNDER);
! 841:
! 842: for (i = 0; i < len; i++) {
! 843: if ('\\' == word[i]) {
! 844: pescape(p, word, &i, len);
! 845: continue;
! 846: }
! 847: chara(p, word[i]);
! 848: }
! 849:
! 850: if (p->flags & TERMP_BOLD ||
! 851: p->flags & TERMP_UNDERLINE)
! 852: style(p, TERMSTYLE_CLEAR);
! 853: }
! 854:
! 855:
! 856: /*
! 857: * Add a symbol to the output line buffer.
! 858: */
! 859: static void
! 860: symbola(struct termp *p, enum tsym sym)
! 861: {
! 862:
! 863: assert(p->symtab[sym].sym);
! 864: stringa(p, p->symtab[sym].sym, p->symtab[sym].sz);
! 865: }
! 866:
! 867:
! 868: /*
! 869: * Like chara() but for arbitrary-length buffers. Resize the buffer by
! 870: * a factor of two (if the buffer is less than that) or the buffer's
! 871: * size.
! 872: */
! 873: static void
! 874: stringa(struct termp *p, const char *c, size_t sz)
! 875: {
! 876: size_t s;
! 877:
! 878: s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
! 879:
! 880: assert(c);
! 881: if (p->col + sz >= p->maxcols) {
! 882: p->buf = realloc(p->buf, s);
! 883: if (NULL == p->buf)
! 884: err(1, "realloc");
! 885: p->maxcols = s;
! 886: }
! 887:
! 888: (void)memcpy(&p->buf[p->col], c, sz);
! 889: p->col += sz;
! 890: }
! 891:
! 892:
! 893: /*
! 894: * Insert a single character into the line-buffer. If the buffer's
! 895: * space is exceeded, then allocate more space by doubling the buffer
! 896: * size.
! 897: */
! 898: static void
! 899: chara(struct termp *p, char c)
! 900: {
! 901:
! 902: if (p->col + 1 >= p->maxcols) {
! 903: p->buf = realloc(p->buf, p->maxcols * 2);
! 904: if (NULL == p->buf)
! 905: err(1, "malloc");
! 906: p->maxcols *= 2;
! 907: }
! 908: p->buf[(p->col)++] = c;
! 909: }
CVSweb