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