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