1/**************************************************************************** 2 * Copyright (c) 1998-2003,2004 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 46MODULE_ID("$Id: lib_tparm.c,v 1.68 2004/02/07 20:52:51 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 110typedef struct { 111 union { 112 int num; 113 char *str; 114 } data; 115 bool num_type; 116} stack_frame; 117 118NCURSES_EXPORT_VAR(int) _nc_tparm_err = 0; 119 120static stack_frame stack[STACKSIZE]; 121static int stack_ptr; 122static const char *tparam_base = ""; 123 124#ifdef TRACE 125static const char *tname; 126#endif /* TRACE */ 127 128static char *out_buff; 129static size_t out_size; 130static size_t out_used; 131 132static char *fmt_buff; 133static size_t fmt_size; 134 135#if NO_LEAKS 136NCURSES_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 149static inline void 150get_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 161static inline void 162save_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 174static inline void 175save_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 186static inline void 187save_char(int c) 188{ 189 if (c == 0) 190 c = 0200; 191 get_space(1); 192 out_buff[out_used++] = c; 193} 194 195static inline void 196npush(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 208static inline int 209npop(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 223static inline void 224spush(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 236static inline char * 237spop(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 252static inline const char * 253parse_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 */ 358NCURSES_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 474static inline char * 475tparam_internal(const char *string, va_list ap) 476{ 477#define NUM_VARS 26 478 char *p_is_s[NUM_PARM]; 479 long 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, long int); 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 777NCURSES_EXPORT(char *) 778tparm(NCURSES_CONST char *string,...) 779{ 780 va_list ap; 781 char *result; 782 783 _nc_tparm_err = 0; 784 va_start(ap, string); 785#ifdef TRACE 786 tname = "tparm"; 787#endif /* TRACE */ 788 result = tparam_internal(string, ap); 789 va_end(ap); 790 return result; 791} 792