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.47 2000/10/04 00:57:13 tom 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 if (out_buff == 0 && (out_buff = typeCalloc(char, 1)) == NULL) 721 return (NULL); 722 out_buff[out_used] = '\0'; 723 724 T((T_RETURN("%s"), _nc_visbuf(out_buff))); 725 return (out_buff); 726 } 727 728 char * 729 tparm(NCURSES_CONST char *string,...) 730 { 731 va_list ap; 732 char *result; 733 734 va_start(ap, string); 735 #ifdef TRACE 736 tname = "tparm"; 737 #endif /* TRACE */ 738 result = tparam_internal(string, ap); 739 va_end(ap); 740 return result; 741 } 742