ncurses 5.2
[ncurses.git] / ncurses / tinfo / lib_tparm.c
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.48 2000/10/14 17:45:00 Sergei.Ivanov 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     get_space(1);
721     out_buff[out_used] = '\0';
722
723     T((T_RETURN("%s"), _nc_visbuf(out_buff)));
724     return (out_buff);
725 }
726
727 char *
728 tparm(NCURSES_CONST char *string,...)
729 {
730     va_list ap;
731     char *result;
732
733     va_start(ap, string);
734 #ifdef TRACE
735     tname = "tparm";
736 #endif /* TRACE */
737     result = tparam_internal(string, ap);
738     va_end(ap);
739     return result;
740 }