ncurses 5.0
[ncurses.git] / ncurses / tinfo / lib_tparm.c
1 /****************************************************************************
2  * Copyright (c) 1998 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 /*
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.39 1999/06/06 00:04: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  *
78  *           %p[1-9]   push ith parm
79  *           %P[a-z]   set dynamic variable [a-z] to pop()
80  *           %g[a-z]   get dynamic variable [a-z] and push it
81  *           %P[A-Z]   set static variable [A-Z] to pop()
82  *           %g[A-Z]   get static variable [A-Z] and push it
83  *           %l        push strlen(pop)
84  *           %'c'      push char constant c
85  *           %{nn}     push integer constant nn
86  *
87  *           %+ %- %* %/ %m
88  *                     arithmetic (%m is mod): push(pop() op pop())
89  *           %& %| %^  bit operations: push(pop() op pop())
90  *           %= %> %<  logical operations: push(pop() op pop())
91  *           %A %O     logical and & or operations for conditionals
92  *           %! %~     unary operations push(op pop())
93  *           %i        add 1 to first two parms (for ANSI terminals)
94  *
95  *           %? expr %t thenpart %e elsepart %;
96  *                     if-then-else, %e elsepart is optional.
97  *                     else-if's are possible ala Algol 68:
98  *                     %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %;
99  *
100  *      For those of the above operators which are binary and not commutative,
101  *      the stack works in the usual way, with
102  *                      %gx %gy %m
103  *      resulting in x mod y, not the reverse.
104  */
105
106 #define STACKSIZE       20
107
108 typedef union {
109         unsigned int    num;
110         char           *str;
111 } stack_frame;
112
113 static  stack_frame     stack[STACKSIZE];
114 static  int     stack_ptr;
115 #ifdef TRACE
116 static const char *tname;
117 #endif /* TRACE */
118
119 static char  *out_buff;
120 static size_t out_size;
121 static size_t out_used;
122
123 #if NO_LEAKS
124 void _nc_free_tparm(void)
125 {
126         if (out_buff != 0) {
127                 FreeAndNull(out_buff);
128                 out_size = 0;
129                 out_used = 0;
130         }
131 }
132 #endif
133
134 static void really_get_space(size_t need)
135 {
136         out_size = need * 2;
137         out_buff = typeRealloc(char, out_size, out_buff);
138         if (out_buff == 0)
139                 _nc_err_abort("Out of memory");
140 }
141
142 static inline void get_space(size_t need)
143 {
144         need += out_used;
145         if (need > out_size)
146                 really_get_space(need);
147 }
148
149 static inline void save_text(const char *fmt, char *s, int len)
150 {
151         size_t s_len = strlen(s);
152         if (len > (int)s_len)
153                 s_len = len;
154
155         get_space(s_len + 1);
156
157         (void)sprintf(out_buff+out_used, fmt, s);
158         out_used += strlen(out_buff+out_used);
159 }
160
161 static inline void save_number(const char *fmt, int number, int len)
162 {
163         if (len < 30)
164                 len = 30; /* actually log10(MAX_INT)+1 */
165
166         get_space(len + 1);
167
168         (void)sprintf(out_buff+out_used, fmt, number);
169         out_used += strlen(out_buff+out_used);
170 }
171
172 static inline void save_char(int c)
173 {
174         if (c == 0)
175                 c = 0200;
176         get_space(1);
177         out_buff[out_used++] = c;
178 }
179
180 static inline void npush(int x)
181 {
182         if (stack_ptr < STACKSIZE) {
183                 stack[stack_ptr].num = x;
184                 stack_ptr++;
185         }
186 }
187
188 static inline int npop(void)
189 {
190         return   (stack_ptr > 0  ?  stack[--stack_ptr].num  :  0);
191 }
192
193 static inline char *spop(void)
194 {
195         static char dummy[] = "";       /* avoid const-cast */
196         return   (stack_ptr > 0  ?  stack[--stack_ptr].str  :  dummy);
197 }
198
199 static inline const char *parse_format(const char *s, char *format, int *len)
200 {
201         bool done = FALSE;
202         bool allowminus = FALSE;
203         bool dot = FALSE;
204         int prec  = 0;
205         int width = 0;
206
207         *len = 0;
208         *format++ = '%';
209         while (*s != '\0' && !done) {
210                 switch (*s) {
211                 case 'c':       /* FALLTHRU */
212                 case 'd':       /* FALLTHRU */
213                 case 'o':       /* FALLTHRU */
214                 case 'x':       /* FALLTHRU */
215                 case 'X':       /* FALLTHRU */
216                 case 's':
217                         *format++ = *s;
218                         done = TRUE;
219                         break;
220                 case '.':
221                         *format++ = *s++;
222                         dot = TRUE;
223                         break;
224                 case '#':
225                         *format++ = *s++;
226                         break;
227                 case ' ':
228                         *format++ = *s++;
229                         break;
230                 case ':':
231                         s++;
232                         allowminus = TRUE;
233                         break;
234                 case '-':
235                         if (allowminus) {
236                                 *format++ = *s++;
237                         } else {
238                                 done = TRUE;
239                         }
240                         break;
241                 default:
242                         if (isdigit(*s)) {
243                                 if (dot)
244                                         prec  = (prec * 10) + (*s - '0');
245                                 else
246                                         width = (width * 10) + (*s - '0');
247                                 *format++ = *s++;
248                         } else {
249                                 done = TRUE;
250                         }
251                 }
252         }
253         *format = '\0';
254         /* return maximum string length in print */
255         *len = (prec > width) ? prec : width;
256         return s;
257 }
258
259 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z')
260 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z')
261
262 static inline char *tparam_internal(const char *string, va_list ap)
263 {
264 #define NUM_VARS 26
265 int     param[9];
266 int     popcount;
267 int     number;
268 int     len;
269 int     level;
270 int     x, y;
271 int     i;
272 register const char *cp;
273 static  size_t len_fmt;
274 static  char *format;
275 static  int dynamic_var[NUM_VARS];
276 static  int static_vars[NUM_VARS];
277
278         out_used = 0;
279         if (string == NULL)
280                 return NULL;
281
282         /*
283          * Find the highest parameter-number referred to in the format string.
284          * Use this value to limit the number of arguments copied from the
285          * variable-length argument list.
286          */
287         for (cp = string, popcount = number = 0; *cp != '\0'; cp++) {
288                 if (cp[0] == '%' && cp[1] != '\0') {
289                         switch (cp[1]) {
290                         case '%':
291                                 cp++;
292                                 break;
293                         case 'i':
294                                 if (popcount < 2)
295                                         popcount = 2;
296                                 break;
297                         case 'p':
298                                 cp++;
299                                 if (cp[1] >= '1' && cp[1] <= '9') {
300                                         int c = cp[1] - '0';
301                                         if (c > popcount)
302                                                 popcount = c;
303                                 }
304                                 break;
305                         case '0': case '1': case '2': case '3': case '4':
306                         case '5': case '6': case '7': case '8': case '9':
307                         case 'd': case 'c': case 's':
308                                 ++number;
309                                 break;
310                         }
311                 }
312         }
313         if ((size_t)(cp - string) > len_fmt) {
314                 len_fmt = (cp - string) + len_fmt + 2;
315                 if ((format = typeRealloc(char, len_fmt, format)) == 0)
316                         return 0;
317         }
318
319         if (number > 9) number = 9;
320         for (i = 0; i < max(popcount, number); i++) {
321                 /*
322                  * FIXME: potential loss here if sizeof(int) != sizeof(char *).
323                  * A few caps (such as plab_norm) have string-valued parms.
324                  */
325                 param[i] = va_arg(ap, int);
326         }
327
328         /*
329          * This is a termcap compatibility hack.  If there are no explicit pop
330          * operations in the string, load the stack in such a way that
331          * successive pops will grab successive parameters.  That will make
332          * the expansion of (for example) \E[%d;%dH work correctly in termcap
333          * style, which means tparam() will expand termcap strings OK.
334          */
335         stack_ptr = 0;
336         if (popcount == 0) {
337                 popcount = number;
338                 for (i = number - 1; i >= 0; i--)
339                         npush(param[i]);
340         }
341
342 #ifdef TRACE
343         if (_nc_tracing & TRACE_CALLS) {
344                 for (i = 0; i < popcount; i++)
345                         save_number(", %d", param[i], 0);
346                 _tracef(T_CALLED("%s(%s%s)"), tname, _nc_visbuf(string), out_buff);
347                 out_used = 0;
348         }
349 #endif /* TRACE */
350
351         while (*string) {
352                 if (*string != '%') {
353                         save_char(*string);
354                 } else {
355                         string++;
356                         string = parse_format(string, format, &len);
357                         switch (*string) {
358                         default:
359                                 break;
360                         case '%':
361                                 save_char('%');
362                                 break;
363
364                         case 'd':       /* FALLTHRU */
365                         case 'o':       /* FALLTHRU */
366                         case 'x':       /* FALLTHRU */
367                         case 'X':       /* FALLTHRU */
368                         case 'c':
369                                 save_number(format, npop(), len);
370                                 break;
371
372                         case 'l':
373                                 save_number("%d", strlen(spop()), 0);
374                                 break;
375
376                         case 's':
377                                 save_text(format, spop(), len);
378                                 break;
379
380                         case 'p':
381                                 string++;
382                                 if (*string >= '1'  &&  *string <= '9')
383                                         npush(param[*string - '1']);
384                                 break;
385
386                         case 'P':
387                                 string++;
388                                 if (isUPPER(*string)) {
389                                         i = (*string - 'A');
390                                         static_vars[i] = npop();
391                                 } else if (isLOWER(*string)) {
392                                         i = (*string - 'a');
393                                         dynamic_var[i] = npop();
394                                 }
395                                 break;
396
397                         case 'g':
398                                 string++;
399                                 if (isUPPER(*string)) {
400                                         i = (*string - 'A');
401                                         npush(static_vars[i]);
402                                 } else if (isLOWER(*string)) {
403                                         i = (*string - 'a');
404                                         npush(dynamic_var[i]);
405                                 }
406                                 break;
407
408                         case S_QUOTE:
409                                 string++;
410                                 npush(*string);
411                                 string++;
412                                 break;
413
414                         case L_BRACE:
415                                 number = 0;
416                                 string++;
417                                 while (*string >= '0'  &&  *string <= '9') {
418                                         number = number * 10 + *string - '0';
419                                         string++;
420                                 }
421                                 npush(number);
422                                 break;
423
424                         case '+':
425                                 npush(npop() + npop());
426                                 break;
427
428                         case '-':
429                                 y = npop();
430                                 x = npop();
431                                 npush(x - y);
432                                 break;
433
434                         case '*':
435                                 npush(npop() * npop());
436                                 break;
437
438                         case '/':
439                                 y = npop();
440                                 x = npop();
441                                 npush(y ? (x / y) : 0);
442                                 break;
443
444                         case 'm':
445                                 y = npop();
446                                 x = npop();
447                                 npush(y ? (x % y) : 0);
448                                 break;
449
450                         case 'A':
451                                 npush(npop() && npop());
452                                 break;
453
454                         case 'O':
455                                 npush(npop() || npop());
456                                 break;
457
458                         case '&':
459                                 npush(npop() & npop());
460                                 break;
461
462                         case '|':
463                                 npush(npop() | npop());
464                                 break;
465
466                         case '^':
467                                 npush(npop() ^ npop());
468                                 break;
469
470                         case '=':
471                                 y = npop();
472                                 x = npop();
473                                 npush(x == y);
474                                 break;
475
476                         case '<':
477                                 y = npop();
478                                 x = npop();
479                                 npush(x < y);
480                                 break;
481
482                         case '>':
483                                 y = npop();
484                                 x = npop();
485                                 npush(x > y);
486                                 break;
487
488                         case '!':
489                                 npush(! npop());
490                                 break;
491
492                         case '~':
493                                 npush(~ npop());
494                                 break;
495
496                         case 'i':
497                                 param[0]++;
498                                 param[1]++;
499                                 break;
500
501                         case '?':
502                                 break;
503
504                         case 't':
505                                 x = npop();
506                                 if (!x) {
507                                         /* scan forward for %e or %; at level zero */
508                                         string++;
509                                         level = 0;
510                                         while (*string) {
511                                                 if (*string == '%') {
512                                                         string++;
513                                                         if (*string == '?')
514                                                                 level++;
515                                                         else if (*string == ';') {
516                                                                 if (level > 0)
517                                                                         level--;
518                                                                 else
519                                                                         break;
520                                                         }
521                                                         else if (*string == 'e'  && level == 0)
522                                                                 break;
523                                                 }
524
525                                                 if (*string)
526                                                         string++;
527                                         }
528                                 }
529                                 break;
530
531                         case 'e':
532                                 /* scan forward for a %; at level zero */
533                                 string++;
534                                 level = 0;
535                                 while (*string) {
536                                         if (*string == '%') {
537                                                 string++;
538                                                 if (*string == '?')
539                                                         level++;
540                                                 else if (*string == ';') {
541                                                         if (level > 0)
542                                                                 level--;
543                                                         else
544                                                                 break;
545                                                 }
546                                         }
547
548                                         if (*string)
549                                                 string++;
550                                 }
551                                 break;
552
553                         case ';':
554                                 break;
555
556                         } /* endswitch (*string) */
557                 } /* endelse (*string == '%') */
558
559                 if (*string == '\0')
560                         break;
561
562                 string++;
563         } /* endwhile (*string) */
564
565         if (out_buff == 0 && (out_buff = typeCalloc(char,1)) == NULL)
566                 return(NULL);
567         out_buff[out_used] = '\0';
568
569         T((T_RETURN("%s"), _nc_visbuf(out_buff)));
570         return(out_buff);
571 }
572
573 char *tparm(NCURSES_CONST char *string, ...)
574 {
575 va_list ap;
576 char *result;
577
578         va_start(ap, string);
579 #ifdef TRACE
580         tname = "tparm";
581 #endif /* TRACE */
582         result = tparam_internal(string, ap);
583         va_end(ap);
584         return result;
585 }