xref: /openbsd/lib/libcurses/tinfo/lib_tparm.c (revision 404b540a)
1 /*	$OpenBSD: lib_tparm.c,v 1.8 2003/03/17 19:16:59 millert Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998,2000 Free Software Foundation, Inc.                   *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
33  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
34  ****************************************************************************/
35 
36 /*
37  *	tparm.c
38  *
39  */
40 
41 #include <curses.priv.h>
42 
43 #include <ctype.h>
44 #include <term.h>
45 #include <tic.h>
46 
47 MODULE_ID("$From: lib_tparm.c,v 1.51 2000/12/10 02:55:08 tom Exp $")
48 
49 /*
50  *	char *
51  *	tparm(string, ...)
52  *
53  *	Substitute the given parameters into the given string by the following
54  *	rules (taken from terminfo(5)):
55  *
56  *	     Cursor addressing and other strings  requiring  parame-
57  *	ters in the terminal are described by a parameterized string
58  *	capability, with like escapes %x in  it.   For  example,  to
59  *	address  the  cursor, the cup capability is given, using two
60  *	parameters: the row and column to  address  to.   (Rows  and
61  *	columns  are  numbered  from  zero and refer to the physical
62  *	screen visible to the user, not to any  unseen  memory.)  If
63  *	the terminal has memory relative cursor addressing, that can
64  *	be indicated by
65  *
66  *	     The parameter mechanism uses  a  stack  and  special  %
67  *	codes  to manipulate it.  Typically a sequence will push one
68  *	of the parameters onto the stack and then print it  in  some
69  *	format.  Often more complex operations are necessary.
70  *
71  *	     The % encodings have the following meanings:
72  *
73  *	     %%        outputs `%'
74  *	     %c        print pop() like %c in printf()
75  *	     %s        print pop() like %s in printf()
76  *           %[[:]flags][width[.precision]][doxXs]
77  *                     as in printf, flags are [-+#] and space
78  *                     The ':' is used to avoid making %+ or %-
79  *                     patterns (see below).
80  *
81  *	     %p[1-9]   push ith parm
82  *	     %P[a-z]   set dynamic variable [a-z] to pop()
83  *	     %g[a-z]   get dynamic variable [a-z] and push it
84  *	     %P[A-Z]   set static variable [A-Z] to pop()
85  *	     %g[A-Z]   get static variable [A-Z] and push it
86  *	     %l        push strlen(pop)
87  *	     %'c'      push char constant c
88  *	     %{nn}     push integer constant nn
89  *
90  *	     %+ %- %* %/ %m
91  *	               arithmetic (%m is mod): push(pop() op pop())
92  *	     %& %| %^  bit operations: push(pop() op pop())
93  *	     %= %> %<  logical operations: push(pop() op pop())
94  *	     %A %O     logical and & or operations for conditionals
95  *	     %! %~     unary operations push(op pop())
96  *	     %i        add 1 to first two parms (for ANSI terminals)
97  *
98  *	     %? expr %t thenpart %e elsepart %;
99  *	               if-then-else, %e elsepart is optional.
100  *	               else-if's are possible ala Algol 68:
101  *	               %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
102  *
103  *	For those of the above operators which are binary and not commutative,
104  *	the stack works in the usual way, with
105  *			%gx %gy %m
106  *	resulting in x mod y, not the reverse.
107  */
108 
109 #define STACKSIZE	20
110 
111 typedef struct {
112     union {
113 	unsigned int num;
114 	char *str;
115     } data;
116     bool num_type;
117 } stack_frame;
118 
119 static stack_frame stack[STACKSIZE];
120 static int stack_ptr;
121 
122 #ifdef TRACE
123 static const char *tname;
124 #endif /* TRACE */
125 
126 static char *out_buff;
127 static size_t out_size;
128 static size_t out_used;
129 
130 #if NO_LEAKS
131 NCURSES_EXPORT(void)
132 _nc_free_tparm(void)
133 {
134     if (out_buff != 0) {
135 	FreeAndNull(out_buff);
136 	out_size = 0;
137 	out_used = 0;
138     }
139 }
140 #endif
141 
142 static void
143 really_get_space(size_t need)
144 {
145     out_size = need * 2;
146     out_buff = typeRealloc(char, out_size, out_buff);
147     if (out_buff == 0)
148 	_nc_err_abort("Out of memory");
149 }
150 
151 static inline void
152 get_space(size_t need)
153 {
154     need += out_used;
155     if (need > out_size)
156 	really_get_space(need);
157 }
158 
159 static inline void
160 save_text(const char *fmt, const char *s, int len)
161 {
162     size_t s_len = strlen(s);
163     if (len > (int) s_len)
164 	s_len = len;
165 
166     get_space(s_len + 1);
167 
168     (void) snprintf(out_buff + out_used, out_size - out_used, fmt, s);
169     out_used += strlen(out_buff + out_used);
170 }
171 
172 static inline void
173 save_number(const char *fmt, int number, int len)
174 {
175     if (len < 30)
176 	len = 30;		/* actually log10(MAX_INT)+1 */
177 
178     get_space(len + 1);
179 
180     (void) snprintf(out_buff + out_used, out_size - out_used, fmt, number);
181     out_used += strlen(out_buff + out_used);
182 }
183 
184 static inline void
185 save_char(int c)
186 {
187     if (c == 0)
188 	c = 0200;
189     get_space(1);
190     out_buff[out_used++] = c;
191 }
192 
193 static inline void
194 npush(int x)
195 {
196     if (stack_ptr < STACKSIZE) {
197 	stack[stack_ptr].num_type = TRUE;
198 	stack[stack_ptr].data.num = x;
199 	stack_ptr++;
200     }
201 }
202 
203 static inline int
204 npop(void)
205 {
206     int result = 0;
207     if (stack_ptr > 0) {
208 	stack_ptr--;
209 	if (stack[stack_ptr].num_type)
210 	    result = stack[stack_ptr].data.num;
211     }
212     return result;
213 }
214 
215 static inline void
216 spush(char *x)
217 {
218     if (stack_ptr < STACKSIZE) {
219 	stack[stack_ptr].num_type = FALSE;
220 	stack[stack_ptr].data.str = x;
221 	stack_ptr++;
222     }
223 }
224 
225 static inline char *
226 spop(void)
227 {
228     static char dummy[] = "";	/* avoid const-cast */
229     char *result = dummy;
230     if (stack_ptr > 0) {
231 	stack_ptr--;
232 	if (!stack[stack_ptr].num_type && stack[stack_ptr].data.str != 0)
233 	    result = stack[stack_ptr].data.str;
234     }
235     return result;
236 }
237 
238 static inline const char *
239 parse_format(const char *s, char *format, int *len)
240 {
241     bool done = FALSE;
242     bool allowminus = FALSE;
243     bool dot = FALSE;
244     bool err = FALSE;
245     char *fmt = format;
246     int prec = 0;
247     int width = 0;
248     int value = 0;
249 
250     *len = 0;
251     *format++ = '%';
252     while (*s != '\0' && !done) {
253 	switch (*s) {
254 	case 'c':		/* FALLTHRU */
255 	case 'd':		/* FALLTHRU */
256 	case 'o':		/* FALLTHRU */
257 	case 'x':		/* FALLTHRU */
258 	case 'X':		/* FALLTHRU */
259 	case 's':
260 	    *format++ = *s;
261 	    done = TRUE;
262 	    break;
263 	case '.':
264 	    *format++ = *s++;
265 	    if (dot) {
266 		err = TRUE;
267 	    } else {
268 		dot = TRUE;
269 		prec = value;
270 	    }
271 	    value = 0;
272 	    break;
273 	case '#':
274 	    *format++ = *s++;
275 	    break;
276 	case ' ':
277 	    *format++ = *s++;
278 	    break;
279 	case ':':
280 	    s++;
281 	    allowminus = TRUE;
282 	    break;
283 	case '-':
284 	    if (allowminus) {
285 		*format++ = *s++;
286 	    } else {
287 		done = TRUE;
288 	    }
289 	    break;
290 	default:
291 	    if (isdigit(CharOf(*s))) {
292 		value = (value * 10) + (*s - '0');
293 		if (value > 10000)
294 		    err = TRUE;
295 		*format++ = *s++;
296 	    } else {
297 		done = TRUE;
298 	    }
299 	}
300     }
301 
302     /*
303      * If we found an error, ignore (and remove) the flags.
304      */
305     if (err) {
306 	prec = width = value = 0;
307 	format = fmt;
308 	*format++ = '%';
309 	*format++ = *s;
310     }
311 
312     if (dot)
313 	width = value;
314     else
315 	prec = value;
316 
317     *format = '\0';
318     /* return maximum string length in print */
319     *len = (prec > width) ? prec : width;
320     return s;
321 }
322 
323 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
324 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
325 
326 static inline char *
327 tparam_internal(const char *string, va_list ap)
328 {
329 #define NUM_VARS 26
330     char *p_is_s[9];
331     int param[9];
332     int lastpop;
333     int popcount;
334     int number;
335     int len;
336     int level;
337     int x, y;
338     int i;
339     size_t len2;
340     register const char *cp;
341     static size_t len_fmt;
342     static char dummy[] = "";
343     static char *format;
344     static int dynamic_var[NUM_VARS];
345     static int static_vars[NUM_VARS];
346 
347     out_used = 0;
348     if (string == NULL)
349 	return NULL;
350 
351     if ((len2 = strlen(string)) > len_fmt) {
352 	len_fmt = len2 + len_fmt + 2;
353 	if ((format = typeRealloc(char, len_fmt, format)) == 0)
354 	      return 0;
355     }
356 
357     /*
358      * Find the highest parameter-number referred to in the format string.
359      * Use this value to limit the number of arguments copied from the
360      * variable-length argument list.
361      */
362 
363     number = 0;
364     lastpop = -1;
365     popcount = 0;
366     memset(p_is_s, 0, sizeof(p_is_s));
367 
368     /*
369      * Analyze the string to see how many parameters we need from the varargs
370      * list, and what their types are.  We will only accept string parameters
371      * if they appear as a %l or %s format following an explicit parameter
372      * reference (e.g., %p2%s).  All other parameters are numbers.
373      *
374      * 'number' counts coarsely the number of pop's we see in the string, and
375      * 'popcount' shows the highest parameter number in the string.  We would
376      * like to simply use the latter count, but if we are reading termcap
377      * strings, there may be cases that we cannot see the explicit parameter
378      * numbers.
379      */
380     for (cp = string; (cp - string) < (int) len2;) {
381 	if (*cp == '%') {
382 	    cp++;
383 	    cp = parse_format(cp, format, &len);
384 	    switch (*cp) {
385 	    default:
386 		break;
387 
388 	    case 'd':		/* FALLTHRU */
389 	    case 'o':		/* FALLTHRU */
390 	    case 'x':		/* FALLTHRU */
391 	    case 'X':		/* FALLTHRU */
392 	    case 'c':		/* FALLTHRU */
393 		number++;
394 		lastpop = -1;
395 		break;
396 
397 	    case 'l':
398 	    case 's':
399 		if (lastpop > 0)
400 		    p_is_s[lastpop - 1] = dummy;
401 		++number;
402 		break;
403 
404 	    case 'p':
405 		cp++;
406 		i = (*cp - '0');
407 		if (i >= 0 && i <= 9) {
408 		    lastpop = i;
409 		    if (lastpop > popcount)
410 			popcount = lastpop;
411 		}
412 		break;
413 
414 	    case 'P':
415 	    case 'g':
416 		cp++;
417 		break;
418 
419 	    case S_QUOTE:
420 		cp += 2;
421 		lastpop = -1;
422 		break;
423 
424 	    case L_BRACE:
425 		cp++;
426 		while (*cp >= '0' && *cp <= '9') {
427 		    cp++;
428 		}
429 		break;
430 
431 	    case '+':
432 	    case '-':
433 	    case '*':
434 	    case '/':
435 	    case 'm':
436 	    case 'A':
437 	    case 'O':
438 	    case '&':
439 	    case '|':
440 	    case '^':
441 	    case '=':
442 	    case '<':
443 	    case '>':
444 	    case '!':
445 	    case '~':
446 		lastpop = -1;
447 		number += 2;
448 		break;
449 
450 	    case 'i':
451 		lastpop = -1;
452 		if (popcount < 2)
453 		    popcount = 2;
454 		break;
455 	    }
456 	}
457 	if (*cp != '\0')
458 	    cp++;
459     }
460 
461     if (number > 9)
462 	number = 9;
463     for (i = 0; i < max(popcount, number); i++) {
464 	/*
465 	 * A few caps (such as plab_norm) have string-valued parms.
466 	 * We'll have to assume that the caller knows the difference, since
467 	 * a char* and an int may not be the same size on the stack.
468 	 */
469 	if (p_is_s[i] != 0) {
470 	    p_is_s[i] = va_arg(ap, char *);
471 	} else {
472 	    param[i] = va_arg(ap, int);
473 	}
474     }
475 
476     /*
477      * This is a termcap compatibility hack.  If there are no explicit pop
478      * operations in the string, load the stack in such a way that
479      * successive pops will grab successive parameters.  That will make
480      * the expansion of (for example) \E[%d;%dH work correctly in termcap
481      * style, which means tparam() will expand termcap strings OK.
482      */
483     stack_ptr = 0;
484     if (popcount == 0) {
485 	popcount = number;
486 	for (i = number - 1; i >= 0; i--)
487 	    npush(param[i]);
488     }
489 #ifdef TRACE
490     if (_nc_tracing & TRACE_CALLS) {
491 	for (i = 0; i < popcount; i++) {
492 	    if (p_is_s[i] != 0)
493 		save_text(", %s", _nc_visbuf(p_is_s[i]), 0);
494 	    else
495 		save_number(", %d", param[i], 0);
496 	}
497 	_tracef(T_CALLED("%s(%s%s)"), tname, _nc_visbuf(string), out_buff);
498 	out_used = 0;
499     }
500 #endif /* TRACE */
501 
502     while (*string) {
503 	if (*string != '%') {
504 	    save_char(*string);
505 	} else {
506 	    string++;
507 	    string = parse_format(string, format, &len);
508 	    switch (*string) {
509 	    default:
510 		break;
511 	    case '%':
512 		save_char('%');
513 		break;
514 
515 	    case 'd':		/* FALLTHRU */
516 	    case 'o':		/* FALLTHRU */
517 	    case 'x':		/* FALLTHRU */
518 	    case 'X':		/* FALLTHRU */
519 	    case 'c':		/* FALLTHRU */
520 		save_number(format, npop(), len);
521 		break;
522 
523 	    case 'l':
524 		save_number("%d", strlen(spop()), 0);
525 		break;
526 
527 	    case 's':
528 		save_text(format, spop(), len);
529 		break;
530 
531 	    case 'p':
532 		string++;
533 		i = (*string - '1');
534 		if (i >= 0 && i < 9) {
535 		    if (p_is_s[i])
536 			spush(p_is_s[i]);
537 		    else
538 			npush(param[i]);
539 		}
540 		break;
541 
542 	    case 'P':
543 		string++;
544 		if (isUPPER(*string)) {
545 		    i = (*string - 'A');
546 		    static_vars[i] = npop();
547 		} else if (isLOWER(*string)) {
548 		    i = (*string - 'a');
549 		    dynamic_var[i] = npop();
550 		}
551 		break;
552 
553 	    case 'g':
554 		string++;
555 		if (isUPPER(*string)) {
556 		    i = (*string - 'A');
557 		    npush(static_vars[i]);
558 		} else if (isLOWER(*string)) {
559 		    i = (*string - 'a');
560 		    npush(dynamic_var[i]);
561 		}
562 		break;
563 
564 	    case S_QUOTE:
565 		string++;
566 		npush(*string);
567 		string++;
568 		break;
569 
570 	    case L_BRACE:
571 		number = 0;
572 		string++;
573 		while (*string >= '0' && *string <= '9') {
574 		    number = number * 10 + *string - '0';
575 		    string++;
576 		}
577 		npush(number);
578 		break;
579 
580 	    case '+':
581 		npush(npop() + npop());
582 		break;
583 
584 	    case '-':
585 		y = npop();
586 		x = npop();
587 		npush(x - y);
588 		break;
589 
590 	    case '*':
591 		npush(npop() * npop());
592 		break;
593 
594 	    case '/':
595 		y = npop();
596 		x = npop();
597 		npush(y ? (x / y) : 0);
598 		break;
599 
600 	    case 'm':
601 		y = npop();
602 		x = npop();
603 		npush(y ? (x % y) : 0);
604 		break;
605 
606 	    case 'A':
607 		npush(npop() && npop());
608 		break;
609 
610 	    case 'O':
611 		npush(npop() || npop());
612 		break;
613 
614 	    case '&':
615 		npush(npop() & npop());
616 		break;
617 
618 	    case '|':
619 		npush(npop() | npop());
620 		break;
621 
622 	    case '^':
623 		npush(npop() ^ npop());
624 		break;
625 
626 	    case '=':
627 		y = npop();
628 		x = npop();
629 		npush(x == y);
630 		break;
631 
632 	    case '<':
633 		y = npop();
634 		x = npop();
635 		npush(x < y);
636 		break;
637 
638 	    case '>':
639 		y = npop();
640 		x = npop();
641 		npush(x > y);
642 		break;
643 
644 	    case '!':
645 		npush(!npop());
646 		break;
647 
648 	    case '~':
649 		npush(~npop());
650 		break;
651 
652 	    case 'i':
653 		if (p_is_s[0] == 0)
654 		    param[0]++;
655 		if (p_is_s[1] == 0)
656 		    param[1]++;
657 		break;
658 
659 	    case '?':
660 		break;
661 
662 	    case 't':
663 		x = npop();
664 		if (!x) {
665 		    /* scan forward for %e or %; at level zero */
666 		    string++;
667 		    level = 0;
668 		    while (*string) {
669 			if (*string == '%') {
670 			    string++;
671 			    if (*string == '?')
672 				level++;
673 			    else if (*string == ';') {
674 				if (level > 0)
675 				    level--;
676 				else
677 				    break;
678 			    } else if (*string == 'e' && level == 0)
679 				break;
680 			}
681 
682 			if (*string)
683 			    string++;
684 		    }
685 		}
686 		break;
687 
688 	    case 'e':
689 		/* scan forward for a %; at level zero */
690 		string++;
691 		level = 0;
692 		while (*string) {
693 		    if (*string == '%') {
694 			string++;
695 			if (*string == '?')
696 			    level++;
697 			else if (*string == ';') {
698 			    if (level > 0)
699 				level--;
700 			    else
701 				break;
702 			}
703 		    }
704 
705 		    if (*string)
706 			string++;
707 		}
708 		break;
709 
710 	    case ';':
711 		break;
712 
713 	    }			/* endswitch (*string) */
714 	}			/* endelse (*string == '%') */
715 
716 	if (*string == '\0')
717 	    break;
718 
719 	string++;
720     }				/* endwhile (*string) */
721 
722     get_space(1);
723     out_buff[out_used] = '\0';
724 
725     T((T_RETURN("%s"), _nc_visbuf(out_buff)));
726     return (out_buff);
727 }
728 
729 NCURSES_EXPORT(char *)
730 tparm
731 (NCURSES_CONST char *string,...)
732 {
733     va_list ap;
734     char *result;
735 
736     va_start(ap, string);
737 #ifdef TRACE
738     tname = "tparm";
739 #endif /* TRACE */
740     result = tparam_internal(string, ap);
741     va_end(ap);
742     return result;
743 }
744