]> git.cameronkatri.com Git - mandoc.git/blob - terminal.c
Added man validator, renamed mdoc validator.
[mandoc.git] / terminal.c
1 /* $Id: terminal.c,v 1.7 2009/03/23 15:20:51 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 #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
32 static struct termp *term_alloc(enum termenc);
33 static void term_free(struct termp *);
34 static void term_body(struct termp *, struct termpair *,
35 const struct mdoc_meta *,
36 const struct mdoc_node *);
37 static void term_head(struct termp *,
38 const struct mdoc_meta *);
39 static void term_foot(struct termp *,
40 const struct mdoc_meta *);
41 static void term_pword(struct termp *, const char *, int);
42 static void term_pescape(struct termp *,
43 const char *, int *, int);
44 static void term_nescape(struct termp *,
45 const char *, size_t);
46 static void term_chara(struct termp *, char);
47 static void term_stringa(struct termp *,
48 const char *, size_t);
49 static int term_isopendelim(const char *, int);
50 static int term_isclosedelim(const char *, int);
51 static void sanity(const struct mdoc_node *); /* XXX */
52
53
54 void *
55 latin1_alloc(void)
56 {
57
58 return(term_alloc(TERMENC_LATIN1));
59 }
60
61
62 void *
63 utf8_alloc(void)
64 {
65
66 return(term_alloc(TERMENC_UTF8));
67 }
68
69
70 void *
71 ascii_alloc(void)
72 {
73
74 return(term_alloc(TERMENC_ASCII));
75 }
76
77
78 int
79 terminal_run(void *arg, const struct man *man,
80 const struct mdoc *mdoc)
81 {
82 struct termp *p;
83
84 if (NULL == mdoc)
85 return(1);
86
87 p = (struct termp *)arg;
88
89 if (NULL == p->symtab)
90 p->symtab = term_ascii2htab();
91
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));
95
96 return(1);
97 }
98
99
100 void
101 terminal_free(void *arg)
102 {
103
104 term_free((struct termp *)arg);
105 }
106
107
108 static void
109 term_free(struct termp *p)
110 {
111
112 if (p->buf)
113 free(p->buf);
114 if (TERMENC_ASCII == p->enc && p->symtab)
115 term_asciifree(p->symtab);
116
117 free(p);
118 }
119
120
121 static struct termp *
122 term_alloc(enum termenc enc)
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
135 static int
136 term_isclosedelim(const char *p, int len)
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
170 term_isopendelim(const char *p, int len)
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
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
226 term_flushln(struct termp *p)
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;
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;
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
350 term_newln(struct termp *p)
351 {
352
353 p->flags |= TERMP_NOSPACE;
354 if (0 == p->col) {
355 p->flags &= ~TERMP_NOLPAD;
356 return;
357 }
358 term_flushln(p);
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
370 term_vspace(struct termp *p)
371 {
372
373 term_newln(p);
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
385 term_word(struct termp *p, const char *word)
386 {
387 int i, j, len;
388
389 len = (int)strlen(word);
390
391 if (p->flags & TERMP_LITERAL) {
392 term_pword(p, word, len);
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);
412 term_pword(p, &word[i - j], j);
413 j = 0;
414 }
415 if (j > 0) {
416 assert(i >= j);
417 term_pword(p, &word[i - j], j);
418 }
419 }
420
421
422 static void
423 term_body(struct termp *p, struct termpair *ppair,
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)
430 term_body(p, ppair, meta, node->next);
431 }
432
433
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 */
440 void
441 term_node(struct termp *p, struct termpair *ppair,
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 */
466 term_word(p, node->string);
467
468 /* Children. */
469
470 if (TERMPAIR_FLAG & pair.type)
471 p->flags |= pair.flag;
472
473 if (dochild && node->child)
474 term_body(p, &pair, meta, node->child);
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
488 term_foot(struct termp *p, const struct mdoc_meta *meta)
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
516 term_vspace(p);
517
518 p->flags |= TERMP_NOSPACE | TERMP_NOBREAK;
519 p->rmargin = p->maxrmargin - strlen(buf);
520 p->offset = 0;
521
522 term_word(p, os);
523 term_flushln(p);
524
525 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
526 p->offset = p->rmargin;
527 p->rmargin = p->maxrmargin;
528 p->flags &= ~TERMP_NOBREAK;
529
530 term_word(p, buf);
531 term_flushln(p);
532
533 free(buf);
534 free(os);
535 }
536
537
538 static void
539 term_head(struct termp *p, const struct mdoc_meta *meta)
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
580 term_word(p, title);
581 term_flushln(p);
582
583 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
584 p->offset = p->rmargin;
585 p->rmargin = p->maxrmargin - strlen(title);
586
587 term_word(p, buf);
588 term_flushln(p);
589
590 p->offset = p->rmargin;
591 p->rmargin = p->maxrmargin;
592 p->flags &= ~TERMP_NOBREAK;
593 p->flags |= TERMP_NOLPAD | TERMP_NOSPACE;
594
595 term_word(p, title);
596 term_flushln(p);
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
613 term_nescape(struct termp *p, const char *word, size_t len)
614 {
615 const char *rhs;
616 size_t sz;
617
618 if (NULL == (rhs = term_a2ascii(p->symtab, word, len, &sz)))
619 return;
620 term_stringa(p, rhs, sz);
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
630 term_pescape(struct termp *p, const char *word, int *i, int len)
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
642 term_nescape(p, &word[*i], 2);
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
657 term_nescape(p, &word[*i], 2);
658 (*i)++;
659 return;
660 case ('['):
661 break;
662 default:
663 term_nescape(p, &word[*i], 1);
664 return;
665 }
666
667 } else if ('[' != word[*i]) {
668 term_nescape(p, &word[*i], 1);
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
679 term_nescape(p, &word[*i - j], (size_t)j);
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
689 term_pword(struct termp *p, const char *word, int len)
690 {
691 int i;
692
693 if (term_isclosedelim(word, len))
694 if ( ! (TERMP_IGNDELIM & p->flags))
695 p->flags |= TERMP_NOSPACE;
696
697 if ( ! (TERMP_NOSPACE & p->flags))
698 term_chara(p, ' ');
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]) {
710 term_pescape(p, word, &i, len);
711 continue;
712 }
713
714 if (TERMP_STYLE & p->flags) {
715 if (TERMP_BOLD & p->flags) {
716 term_chara(p, word[i]);
717 term_chara(p, 8);
718 }
719 if (TERMP_UNDER & p->flags) {
720 term_chara(p, '_');
721 term_chara(p, 8);
722 }
723 }
724
725 term_chara(p, word[i]);
726 }
727
728 if (term_isopendelim(word, len))
729 p->flags |= TERMP_NOSPACE;
730 }
731
732
733 /*
734 * Like term_chara() but for arbitrary-length buffers. Resize the
735 * buffer by a factor of two (if the buffer is less than that) or the
736 * buffer's size.
737 */
738 static void
739 term_stringa(struct termp *p, const char *c, size_t sz)
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
768 term_chara(struct termp *p, char c)
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 }