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