1 /* $OpenBSD: lib_tparm.c,v 1.1 1999/01/18 19:10:20 millert Exp $ */ 2 3 /**************************************************************************** 4 * Copyright (c) 1998 Free Software Foundation, Inc. * 5 * * 6 * Permission is hereby granted, free of charge, to any person obtaining a * 7 * copy of this software and associated documentation files (the * 8 * "Software"), to deal in the Software without restriction, including * 9 * without limitation the rights to use, copy, modify, merge, publish, * 10 * distribute, distribute with modifications, sublicense, and/or sell * 11 * copies of the Software, and to permit persons to whom the Software is * 12 * furnished to do so, subject to the following conditions: * 13 * * 14 * The above copyright notice and this permission notice shall be included * 15 * in all copies or substantial portions of the Software. * 16 * * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 18 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 20 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 21 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 22 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 23 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 24 * * 25 * Except as contained in this notice, the name(s) of the above copyright * 26 * holders shall not be used in advertising or otherwise to promote the * 27 * sale, use or other dealings in this Software without prior written * 28 * authorization. * 29 ****************************************************************************/ 30 31 /**************************************************************************** 32 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 33 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 34 ****************************************************************************/ 35 36 37 /* 38 * tparm.c 39 * 40 */ 41 42 #include <curses.priv.h> 43 44 #include <ctype.h> 45 #include <term.h> 46 #include <tic.h> 47 48 MODULE_ID("$From: lib_tparm.c,v 1.37 1999/01/02 22:38:25 tom Exp $") 49 50 /* 51 * char * 52 * tparm(string, ...) 53 * 54 * Substitute the given parameters into the given string by the following 55 * rules (taken from terminfo(5)): 56 * 57 * Cursor addressing and other strings requiring parame- 58 * ters in the terminal are described by a parameterized string 59 * capability, with like escapes %x in it. For example, to 60 * address the cursor, the cup capability is given, using two 61 * parameters: the row and column to address to. (Rows and 62 * columns are numbered from zero and refer to the physical 63 * screen visible to the user, not to any unseen memory.) If 64 * the terminal has memory relative cursor addressing, that can 65 * be indicated by 66 * 67 * The parameter mechanism uses a stack and special % 68 * codes to manipulate it. Typically a sequence will push one 69 * of the parameters onto the stack and then print it in some 70 * format. Often more complex operations are necessary. 71 * 72 * The % encodings have the following meanings: 73 * 74 * %% outputs `%' 75 * %c print pop() like %c in printf() 76 * %s print pop() like %s in printf() 77 * %[[:]flags][width[.precision]][doxXs] 78 * as in printf, flags are [-+#] and space 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 union { 111 unsigned int num; 112 char *str; 113 } stack_frame; 114 115 static stack_frame stack[STACKSIZE]; 116 static int stack_ptr; 117 #ifdef TRACE 118 static const char *tname; 119 #endif /* TRACE */ 120 121 static char *out_buff; 122 static size_t out_size; 123 static size_t out_used; 124 125 #if NO_LEAKS 126 void _nc_free_tparm(void) 127 { 128 if (out_buff != 0) { 129 FreeAndNull(out_buff); 130 out_size = 0; 131 out_used = 0; 132 } 133 } 134 #endif 135 136 static void really_get_space(size_t need) 137 { 138 out_size = need * 2; 139 out_buff = (char *)_nc_doalloc(out_buff, out_size); 140 if (out_buff == 0) 141 _nc_err_abort("Out of memory"); 142 } 143 144 static inline void get_space(size_t need) 145 { 146 need += out_used; 147 if (need > out_size) 148 really_get_space(need); 149 } 150 151 static inline void save_text(const char *fmt, char *s, int len) 152 { 153 size_t s_len = strlen(s); 154 if (len > (int)s_len) 155 s_len = len; 156 157 get_space(s_len + 1); 158 159 (void)sprintf(out_buff+out_used, fmt, s); 160 out_used += strlen(out_buff+out_used); 161 } 162 163 static inline void save_number(const char *fmt, int number, int len) 164 { 165 if (len < 30) 166 len = 30; /* actually log10(MAX_INT)+1 */ 167 168 get_space(len + 1); 169 170 (void)sprintf(out_buff+out_used, fmt, number); 171 out_used += strlen(out_buff+out_used); 172 } 173 174 static inline void save_char(int c) 175 { 176 if (c == 0) 177 c = 0200; 178 get_space(1); 179 out_buff[out_used++] = c; 180 } 181 182 static inline void npush(int x) 183 { 184 if (stack_ptr < STACKSIZE) { 185 stack[stack_ptr].num = x; 186 stack_ptr++; 187 } 188 } 189 190 static inline int npop(void) 191 { 192 return (stack_ptr > 0 ? stack[--stack_ptr].num : 0); 193 } 194 195 static inline char *spop(void) 196 { 197 static char dummy[] = ""; /* avoid const-cast */ 198 return (stack_ptr > 0 ? stack[--stack_ptr].str : dummy); 199 } 200 201 static inline const char *parse_format(const char *s, char *format, int *len) 202 { 203 bool done = FALSE; 204 bool allowminus = FALSE; 205 bool dot = FALSE; 206 int prec = 0; 207 int width = 0; 208 209 *len = 0; 210 *format++ = '%'; 211 while (*s != '\0' && !done) { 212 switch (*s) { 213 case 'c': /* FALLTHRU */ 214 case 'd': /* FALLTHRU */ 215 case 'o': /* FALLTHRU */ 216 case 'x': /* FALLTHRU */ 217 case 'X': /* FALLTHRU */ 218 case 's': 219 *format++ = *s; 220 done = TRUE; 221 break; 222 case '.': 223 *format++ = *s++; 224 dot = TRUE; 225 break; 226 case '#': 227 *format++ = *s++; 228 break; 229 case ' ': 230 *format++ = *s++; 231 break; 232 case ':': 233 s++; 234 allowminus = TRUE; 235 break; 236 case '-': 237 if (allowminus) { 238 *format++ = *s++; 239 } else { 240 done = TRUE; 241 } 242 break; 243 default: 244 if (isdigit(*s)) { 245 if (dot) 246 prec = (prec * 10) + (*s - '0'); 247 else 248 width = (width * 10) + (*s - '0'); 249 *format++ = *s++; 250 } else { 251 done = TRUE; 252 } 253 } 254 } 255 *format = '\0'; 256 /* return maximum string length in print */ 257 *len = (prec > width) ? prec : width; 258 return s; 259 } 260 261 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z') 262 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z') 263 264 static inline char *tparam_internal(const char *string, va_list ap) 265 { 266 #define NUM_VARS 26 267 int param[9]; 268 int popcount; 269 int number; 270 int len; 271 int level; 272 int x, y; 273 int i; 274 register const char *cp; 275 static size_t len_fmt; 276 static char *format; 277 static int dynamic_var[NUM_VARS]; 278 static int static_vars[NUM_VARS]; 279 280 out_used = 0; 281 if (string == NULL) 282 return NULL; 283 284 /* 285 * Find the highest parameter-number referred to in the format string. 286 * Use this value to limit the number of arguments copied from the 287 * variable-length argument list. 288 */ 289 for (cp = string, popcount = number = 0; *cp != '\0'; cp++) { 290 if (cp[0] == '%' && cp[1] != '\0') { 291 switch (cp[1]) { 292 case '%': 293 cp++; 294 break; 295 case 'i': 296 if (popcount < 2) 297 popcount = 2; 298 break; 299 case 'p': 300 cp++; 301 if (cp[1] >= '1' && cp[1] <= '9') { 302 int c = cp[1] - '0'; 303 if (c > popcount) 304 popcount = c; 305 } 306 break; 307 case '0': case '1': case '2': case '3': case '4': 308 case '5': case '6': case '7': case '8': case '9': 309 case 'd': case 'c': case 's': 310 ++number; 311 break; 312 } 313 } 314 } 315 if ((size_t)(cp - string) > len_fmt) { 316 len_fmt = (cp - string) + len_fmt + 2; 317 if ((format = _nc_doalloc(format, len_fmt)) == 0) 318 return 0; 319 } 320 321 if (number > 9) number = 9; 322 for (i = 0; i < max(popcount, number); i++) { 323 /* 324 * FIXME: potential loss here if sizeof(int) != sizeof(char *). 325 * A few caps (such as plab_norm) have string-valued parms. 326 */ 327 param[i] = va_arg(ap, int); 328 } 329 330 /* 331 * This is a termcap compatibility hack. If there are no explicit pop 332 * operations in the string, load the stack in such a way that 333 * successive pops will grab successive parameters. That will make 334 * the expansion of (for example) \E[%d;%dH work correctly in termcap 335 * style, which means tparam() will expand termcap strings OK. 336 */ 337 stack_ptr = 0; 338 if (popcount == 0) { 339 popcount = number; 340 for (i = number - 1; i >= 0; i--) 341 npush(param[i]); 342 } 343 344 #ifdef TRACE 345 if (_nc_tracing & TRACE_CALLS) { 346 for (i = 0; i < popcount; i++) 347 save_number(", %d", param[i], 0); 348 _tracef(T_CALLED("%s(%s%s)"), tname, _nc_visbuf(string), out_buff); 349 out_used = 0; 350 } 351 #endif /* TRACE */ 352 353 while (*string) { 354 if (*string != '%') { 355 save_char(*string); 356 } else { 357 string++; 358 string = parse_format(string, format, &len); 359 switch (*string) { 360 default: 361 break; 362 case '%': 363 save_char('%'); 364 break; 365 366 case 'd': /* FALLTHRU */ 367 case 'o': /* FALLTHRU */ 368 case 'x': /* FALLTHRU */ 369 case 'X': /* FALLTHRU */ 370 case 'c': 371 save_number(format, npop(), len); 372 break; 373 374 case 'l': 375 save_number("%d", strlen(spop()), 0); 376 break; 377 378 case 's': 379 save_text(format, spop(), len); 380 break; 381 382 case 'p': 383 string++; 384 if (*string >= '1' && *string <= '9') 385 npush(param[*string - '1']); 386 break; 387 388 case 'P': 389 string++; 390 if (isUPPER(*string)) { 391 i = (*string - 'A'); 392 static_vars[i] = npop(); 393 } else if (isLOWER(*string)) { 394 i = (*string - 'a'); 395 dynamic_var[i] = npop(); 396 } 397 break; 398 399 case 'g': 400 string++; 401 if (isUPPER(*string)) { 402 i = (*string - 'A'); 403 npush(static_vars[i]); 404 } else if (isLOWER(*string)) { 405 i = (*string - 'a'); 406 npush(dynamic_var[i]); 407 } 408 break; 409 410 case S_QUOTE: 411 string++; 412 npush(*string); 413 string++; 414 break; 415 416 case L_BRACE: 417 number = 0; 418 string++; 419 while (*string >= '0' && *string <= '9') { 420 number = number * 10 + *string - '0'; 421 string++; 422 } 423 npush(number); 424 break; 425 426 case '+': 427 npush(npop() + npop()); 428 break; 429 430 case '-': 431 y = npop(); 432 x = npop(); 433 npush(x - y); 434 break; 435 436 case '*': 437 npush(npop() * npop()); 438 break; 439 440 case '/': 441 y = npop(); 442 x = npop(); 443 npush(x / y); 444 break; 445 446 case 'm': 447 y = npop(); 448 x = npop(); 449 npush(x % y); 450 break; 451 452 case 'A': 453 npush(npop() && npop()); 454 break; 455 456 case 'O': 457 npush(npop() || npop()); 458 break; 459 460 case '&': 461 npush(npop() & npop()); 462 break; 463 464 case '|': 465 npush(npop() | npop()); 466 break; 467 468 case '^': 469 npush(npop() ^ npop()); 470 break; 471 472 case '=': 473 y = npop(); 474 x = npop(); 475 npush(x == y); 476 break; 477 478 case '<': 479 y = npop(); 480 x = npop(); 481 npush(x < y); 482 break; 483 484 case '>': 485 y = npop(); 486 x = npop(); 487 npush(x > y); 488 break; 489 490 case '!': 491 npush(! npop()); 492 break; 493 494 case '~': 495 npush(~ npop()); 496 break; 497 498 case 'i': 499 param[0]++; 500 param[1]++; 501 break; 502 503 case '?': 504 break; 505 506 case 't': 507 x = npop(); 508 if (!x) { 509 /* scan forward for %e or %; at level zero */ 510 string++; 511 level = 0; 512 while (*string) { 513 if (*string == '%') { 514 string++; 515 if (*string == '?') 516 level++; 517 else if (*string == ';') { 518 if (level > 0) 519 level--; 520 else 521 break; 522 } 523 else if (*string == 'e' && level == 0) 524 break; 525 } 526 527 if (*string) 528 string++; 529 } 530 } 531 break; 532 533 case 'e': 534 /* scan forward for a %; at level zero */ 535 string++; 536 level = 0; 537 while (*string) { 538 if (*string == '%') { 539 string++; 540 if (*string == '?') 541 level++; 542 else if (*string == ';') { 543 if (level > 0) 544 level--; 545 else 546 break; 547 } 548 } 549 550 if (*string) 551 string++; 552 } 553 break; 554 555 case ';': 556 break; 557 558 } /* endswitch (*string) */ 559 } /* endelse (*string == '%') */ 560 561 if (*string == '\0') 562 break; 563 564 string++; 565 } /* endwhile (*string) */ 566 567 if (out_buff == 0 && (out_buff = calloc(1,1)) == NULL) 568 return(NULL); 569 out_buff[out_used] = '\0'; 570 571 T((T_RETURN("%s"), _nc_visbuf(out_buff))); 572 return(out_buff); 573 } 574 575 char *tparm(NCURSES_CONST char *string, ...) 576 { 577 va_list ap; 578 char *result; 579 580 va_start(ap, string); 581 #ifdef TRACE 582 tname = "tparm"; 583 #endif /* TRACE */ 584 result = tparam_internal(string, ap); 585 va_end(ap); 586 return result; 587 } 588