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