]> git.cameronkatri.com Git - mandoc.git/blob - terminal.c
Fixed after-NLINE-error assertion.
[mandoc.git] / terminal.c
1 /* $Id: terminal.c,v 1.10 2009/03/26 14:44:41 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 #include "man.h"
27 #include "mdoc.h"
28
29 extern int man_run(struct termp *,
30 const struct man *);
31 extern int mdoc_run(struct termp *,
32 const struct mdoc *);
33
34 static struct termp *term_alloc(enum termenc);
35 static void term_free(struct termp *);
36 static void term_pword(struct termp *, const char *, int);
37 static void term_pescape(struct termp *,
38 const char *, int *, int);
39 static void term_nescape(struct termp *,
40 const char *, size_t);
41 static void term_chara(struct termp *, char);
42 static void term_stringa(struct termp *,
43 const char *, size_t);
44 static int term_isopendelim(const char *, int);
45 static int term_isclosedelim(const char *, int);
46
47
48 void *
49 ascii_alloc(void)
50 {
51
52 return(term_alloc(TERMENC_ASCII));
53 }
54
55
56 int
57 terminal_run(void *arg, const struct man *man,
58 const struct mdoc *mdoc)
59 {
60 struct termp *p;
61
62 p = (struct termp *)arg;
63
64 if (NULL == p->symtab)
65 p->symtab = term_ascii2htab();
66
67 if (man)
68 return(man_run(p, man));
69 if (mdoc)
70 return(mdoc_run(p, mdoc));
71
72 return(1);
73 }
74
75
76 void
77 terminal_free(void *arg)
78 {
79
80 term_free((struct termp *)arg);
81 }
82
83
84 static void
85 term_free(struct termp *p)
86 {
87
88 if (p->buf)
89 free(p->buf);
90 if (TERMENC_ASCII == p->enc && p->symtab)
91 term_asciifree(p->symtab);
92
93 free(p);
94 }
95
96
97 static struct termp *
98 term_alloc(enum termenc enc)
99 {
100 struct termp *p;
101
102 if (NULL == (p = malloc(sizeof(struct termp))))
103 err(1, "malloc");
104 bzero(p, sizeof(struct termp));
105 p->maxrmargin = 78;
106 p->enc = enc;
107 return(p);
108 }
109
110
111 static int
112 term_isclosedelim(const char *p, int len)
113 {
114
115 if (1 != len)
116 return(0);
117
118 switch (*p) {
119 case('.'):
120 /* FALLTHROUGH */
121 case(','):
122 /* FALLTHROUGH */
123 case(';'):
124 /* FALLTHROUGH */
125 case(':'):
126 /* FALLTHROUGH */
127 case('?'):
128 /* FALLTHROUGH */
129 case('!'):
130 /* FALLTHROUGH */
131 case(')'):
132 /* FALLTHROUGH */
133 case(']'):
134 /* FALLTHROUGH */
135 case('}'):
136 return(1);
137 default:
138 break;
139 }
140
141 return(0);
142 }
143
144
145 static int
146 term_isopendelim(const char *p, int len)
147 {
148
149 if (1 != len)
150 return(0);
151
152 switch (*p) {
153 case('('):
154 /* FALLTHROUGH */
155 case('['):
156 /* FALLTHROUGH */
157 case('{'):
158 return(1);
159 default:
160 break;
161 }
162
163 return(0);
164 }
165
166
167 /*
168 * Flush a line of text. A "line" is loosely defined as being something
169 * that should be followed by a newline, regardless of whether it's
170 * broken apart by newlines getting there. A line can also be a
171 * fragment of a columnar list.
172 *
173 * Specifically, a line is whatever's in p->buf of length p->col, which
174 * is zeroed after this function returns.
175 *
176 * The variables TERMP_NOLPAD, TERMP_LITERAL and TERMP_NOBREAK are of
177 * critical importance here. Their behaviour follows:
178 *
179 * - TERMP_NOLPAD: when beginning to write the line, don't left-pad the
180 * offset value. This is useful when doing columnar lists where the
181 * prior column has right-padded.
182 *
183 * - TERMP_NOBREAK: this is the most important and is used when making
184 * columns. In short: don't print a newline and instead pad to the
185 * right margin. Used in conjunction with TERMP_NOLPAD.
186 *
187 * - TERMP_NONOBREAK: don't newline when TERMP_NOBREAK is specified.
188 *
189 * In-line line breaking:
190 *
191 * If TERMP_NOBREAK is specified and the line overruns the right
192 * margin, it will break and pad-right to the right margin after
193 * writing. If maxrmargin is violated, it will break and continue
194 * writing from the right-margin, which will lead to the above
195 * scenario upon exit.
196 *
197 * Otherwise, the line will break at the right margin. Extremely long
198 * lines will cause the system to emit a warning (TODO: hyphenate, if
199 * possible).
200 */
201 void
202 term_flushln(struct termp *p)
203 {
204 int i, j;
205 size_t vsz, vis, maxvis, mmax, bp;
206
207 /*
208 * First, establish the maximum columns of "visible" content.
209 * This is usually the difference between the right-margin and
210 * an indentation, but can be, for tagged lists or columns, a
211 * small set of values.
212 */
213
214 assert(p->offset < p->rmargin);
215 maxvis = p->rmargin - p->offset;
216 mmax = p->maxrmargin - p->offset;
217 bp = TERMP_NOBREAK & p->flags ? mmax : maxvis;
218 vis = 0;
219
220 /*
221 * If in the standard case (left-justified), then begin with our
222 * indentation, otherwise (columns, etc.) just start spitting
223 * out text.
224 */
225
226 if ( ! (p->flags & TERMP_NOLPAD))
227 /* LINTED */
228 for (j = 0; j < (int)p->offset; j++)
229 putchar(' ');
230
231 for (i = 0; i < (int)p->col; i++) {
232 /*
233 * Count up visible word characters. Control sequences
234 * (starting with the CSI) aren't counted. A space
235 * generates a non-printing word, which is valid (the
236 * space is printed according to regular spacing rules).
237 */
238
239 /* LINTED */
240 for (j = i, vsz = 0; j < (int)p->col; j++) {
241 if (' ' == p->buf[j])
242 break;
243 else if (8 == p->buf[j])
244 j += 1;
245 else
246 vsz++;
247 }
248
249 /*
250 * Do line-breaking. If we're greater than our
251 * break-point and already in-line, break to the next
252 * line and start writing. If we're at the line start,
253 * then write out the word (TODO: hyphenate) and break
254 * in a subsequent loop invocation.
255 */
256
257 if ( ! (TERMP_NOBREAK & p->flags)) {
258 if (vis && vis + vsz > bp) {
259 putchar('\n');
260 for (j = 0; j < (int)p->offset; j++)
261 putchar(' ');
262 vis = 0;
263 }
264 } else if (vis && vis + vsz > bp) {
265 putchar('\n');
266 for (j = 0; j < (int)p->rmargin; j++)
267 putchar(' ');
268 vis = p->rmargin - p->offset;
269 }
270
271 /*
272 * Write out the word and a trailing space. Omit the
273 * space if we're the last word in the line or beyond
274 * our breakpoint.
275 */
276
277 for ( ; i < (int)p->col; i++) {
278 if (' ' == p->buf[i])
279 break;
280 putchar(p->buf[i]);
281 }
282 vis += vsz;
283 if (i < (int)p->col && vis <= bp) {
284 putchar(' ');
285 vis++;
286 }
287 }
288
289 /*
290 * If we've overstepped our maximum visible no-break space, then
291 * cause a newline and offset at the right margin.
292 */
293
294 if ((TERMP_NOBREAK & p->flags) && vis >= maxvis) {
295 if ( ! (TERMP_NONOBREAK & p->flags)) {
296 putchar('\n');
297 for (i = 0; i < (int)p->rmargin; i++)
298 putchar(' ');
299 }
300 p->col = 0;
301 return;
302 }
303
304 /*
305 * If we're not to right-marginalise it (newline), then instead
306 * pad to the right margin and stay off.
307 */
308
309 if (p->flags & TERMP_NOBREAK) {
310 if ( ! (TERMP_NONOBREAK & p->flags))
311 for ( ; vis < maxvis; vis++)
312 putchar(' ');
313 } else
314 putchar('\n');
315
316 p->col = 0;
317 }
318
319
320 /*
321 * A newline only breaks an existing line; it won't assert vertical
322 * space. All data in the output buffer is flushed prior to the newline
323 * assertion.
324 */
325 void
326 term_newln(struct termp *p)
327 {
328
329 p->flags |= TERMP_NOSPACE;
330 if (0 == p->col) {
331 p->flags &= ~TERMP_NOLPAD;
332 return;
333 }
334 term_flushln(p);
335 p->flags &= ~TERMP_NOLPAD;
336 }
337
338
339 /*
340 * Asserts a vertical space (a full, empty line-break between lines).
341 * Note that if used twice, this will cause two blank spaces and so on.
342 * All data in the output buffer is flushed prior to the newline
343 * assertion.
344 */
345 void
346 term_vspace(struct termp *p)
347 {
348
349 term_newln(p);
350 putchar('\n');
351 }
352
353
354 /*
355 * Break apart a word into "pwords" (partial-words, usually from
356 * breaking up a phrase into individual words) and, eventually, put them
357 * into the output buffer. If we're a literal word, then don't break up
358 * the word and put it verbatim into the output buffer.
359 */
360 void
361 term_word(struct termp *p, const char *word)
362 {
363 int i, j, len;
364
365 len = (int)strlen(word);
366
367 if (p->flags & TERMP_LITERAL) {
368 term_pword(p, word, len);
369 return;
370 }
371
372 /* LINTED */
373 for (j = i = 0; i < len; i++) {
374 if (' ' != word[i]) {
375 j++;
376 continue;
377 }
378
379 /* Escaped spaces don't delimit... */
380 if (i && ' ' == word[i] && '\\' == word[i - 1]) {
381 j++;
382 continue;
383 }
384
385 if (0 == j)
386 continue;
387 assert(i >= j);
388 term_pword(p, &word[i - j], j);
389 j = 0;
390 }
391 if (j > 0) {
392 assert(i >= j);
393 term_pword(p, &word[i - j], j);
394 }
395 }
396
397
398 /*
399 * Determine the symbol indicated by an escape sequences, that is, one
400 * starting with a backslash. Once done, we pass this value into the
401 * output buffer by way of the symbol table.
402 */
403 static void
404 term_nescape(struct termp *p, const char *word, size_t len)
405 {
406 const char *rhs;
407 size_t sz;
408
409 if (NULL == (rhs = term_a2ascii(p->symtab, word, len, &sz)))
410 return;
411 term_stringa(p, rhs, sz);
412 }
413
414
415 /*
416 * Handle an escape sequence: determine its length and pass it to the
417 * escape-symbol look table. Note that we assume mdoc(3) has validated
418 * the escape sequence (we assert upon badly-formed escape sequences).
419 */
420 static void
421 term_pescape(struct termp *p, const char *word, int *i, int len)
422 {
423 int j;
424
425 if (++(*i) >= len)
426 return;
427
428 if ('(' == word[*i]) {
429 (*i)++;
430 if (*i + 1 >= len)
431 return;
432
433 term_nescape(p, &word[*i], 2);
434 (*i)++;
435 return;
436
437 } else if ('*' == word[*i]) {
438 (*i)++;
439 if (*i >= len)
440 return;
441
442 switch (word[*i]) {
443 case ('('):
444 (*i)++;
445 if (*i + 1 >= len)
446 return;
447
448 term_nescape(p, &word[*i], 2);
449 (*i)++;
450 return;
451 case ('['):
452 break;
453 default:
454 term_nescape(p, &word[*i], 1);
455 return;
456 }
457
458 } else if ('[' != word[*i]) {
459 term_nescape(p, &word[*i], 1);
460 return;
461 }
462
463 (*i)++;
464 for (j = 0; word[*i] && ']' != word[*i]; (*i)++, j++)
465 /* Loop... */ ;
466
467 if (0 == word[*i])
468 return;
469
470 term_nescape(p, &word[*i - j], (size_t)j);
471 }
472
473
474 /*
475 * Handle pwords, partial words, which may be either a single word or a
476 * phrase that cannot be broken down (such as a literal string). This
477 * handles word styling.
478 */
479 static void
480 term_pword(struct termp *p, const char *word, int len)
481 {
482 int i;
483
484 if (term_isclosedelim(word, len))
485 if ( ! (TERMP_IGNDELIM & p->flags))
486 p->flags |= TERMP_NOSPACE;
487
488 if ( ! (TERMP_NOSPACE & p->flags))
489 term_chara(p, ' ');
490
491 if ( ! (p->flags & TERMP_NONOSPACE))
492 p->flags &= ~TERMP_NOSPACE;
493
494 /*
495 * If ANSI (word-length styling), then apply our style now,
496 * before the word.
497 */
498
499 for (i = 0; i < len; i++) {
500 if ('\\' == word[i]) {
501 term_pescape(p, word, &i, len);
502 continue;
503 }
504
505 if (TERMP_STYLE & p->flags) {
506 if (TERMP_BOLD & p->flags) {
507 term_chara(p, word[i]);
508 term_chara(p, 8);
509 }
510 if (TERMP_UNDER & p->flags) {
511 term_chara(p, '_');
512 term_chara(p, 8);
513 }
514 }
515
516 term_chara(p, word[i]);
517 }
518
519 if (term_isopendelim(word, len))
520 p->flags |= TERMP_NOSPACE;
521 }
522
523
524 /*
525 * Like term_chara() but for arbitrary-length buffers. Resize the
526 * buffer by a factor of two (if the buffer is less than that) or the
527 * buffer's size.
528 */
529 static void
530 term_stringa(struct termp *p, const char *c, size_t sz)
531 {
532 size_t s;
533
534 if (0 == sz)
535 return;
536
537 assert(c);
538 if (p->col + sz >= p->maxcols) {
539 if (0 == p->maxcols)
540 p->maxcols = 256;
541 s = sz > p->maxcols * 2 ? sz : p->maxcols * 2;
542 p->buf = realloc(p->buf, s);
543 if (NULL == p->buf)
544 err(1, "realloc");
545 p->maxcols = s;
546 }
547
548 (void)memcpy(&p->buf[(int)p->col], c, sz);
549 p->col += sz;
550 }
551
552
553 /*
554 * Insert a single character into the line-buffer. If the buffer's
555 * space is exceeded, then allocate more space by doubling the buffer
556 * size.
557 */
558 static void
559 term_chara(struct termp *p, char c)
560 {
561 size_t s;
562
563 if (p->col + 1 >= p->maxcols) {
564 if (0 == p->maxcols)
565 p->maxcols = 256;
566 s = p->maxcols * 2;
567 p->buf = realloc(p->buf, s);
568 if (NULL == p->buf)
569 err(1, "realloc");
570 p->maxcols = s;
571 }
572 p->buf[(int)(p->col)++] = c;
573 }
574