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