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