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