cba31d9fd36b5e7759fcc6353335c60e289da908
[ncurses.git] / ncurses / tinfo / lib_tparm.c
1 /****************************************************************************
2  * Copyright (c) 1998-2005,2006 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
46 MODULE_ID("$Id: lib_tparm.c,v 1.71 2006/11/26 01:12:56 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
110 typedef struct {
111     union {
112         int num;
113         char *str;
114     } data;
115     bool num_type;
116 } stack_frame;
117
118 NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0;
119
120 static stack_frame stack[STACKSIZE];
121 static int stack_ptr;
122 static const char *tparam_base = "";
123
124 #ifdef TRACE
125 static const char *tname;
126 #endif /* TRACE */
127
128 static char *out_buff;
129 static size_t out_size;
130 static size_t out_used;
131
132 static char *fmt_buff;
133 static size_t fmt_size;
134
135 #if NO_LEAKS
136 NCURSES_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
149 static NCURSES_INLINE void
150 get_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
161 static NCURSES_INLINE void
162 save_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
174 static NCURSES_INLINE void
175 save_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
186 static NCURSES_INLINE void
187 save_char(int c)
188 {
189     if (c == 0)
190         c = 0200;
191     get_space(1);
192     out_buff[out_used++] = c;
193 }
194
195 static NCURSES_INLINE void
196 npush(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
208 static NCURSES_INLINE int
209 npop(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
223 static NCURSES_INLINE void
224 spush(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
236 static NCURSES_INLINE char *
237 spop(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
252 static NCURSES_INLINE const char *
253 parse_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  */
358 NCURSES_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
474 static NCURSES_INLINE char *
475 tparam_internal(const char *string, va_list ap)
476 {
477 #define NUM_VARS 26
478     char *p_is_s[NUM_PARM];
479     TPARM_ARG 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, TPARM_ARG);
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
777 #if NCURSES_TPARM_VARARGS
778 #define tparm_varargs tparm
779 #else
780 #define tparm_proto tparm
781 #endif
782
783 NCURSES_EXPORT(char *)
784 tparm_varargs(NCURSES_CONST char *string,...)
785 {
786     va_list ap;
787     char *result;
788
789     _nc_tparm_err = 0;
790     va_start(ap, string);
791 #ifdef TRACE
792     tname = "tparm";
793 #endif /* TRACE */
794     result = tparam_internal(string, ap);
795     va_end(ap);
796     return result;
797 }
798
799 #if !NCURSES_TPARM_VARARGS
800 NCURSES_EXPORT(char *)
801 tparm_proto(NCURSES_CONST char *string,
802             TPARM_ARG a1,
803             TPARM_ARG a2,
804             TPARM_ARG a3,
805             TPARM_ARG a4,
806             TPARM_ARG a5,
807             TPARM_ARG a6,
808             TPARM_ARG a7,
809             TPARM_ARG a8,
810             TPARM_ARG a9)
811 {
812     return tparm_varargs(string, a1, a2, a3, a4, a5, a6, a7, a8, a9);
813 }
814 #endif /* NCURSES_TPARM_VARARGS */