1 /* $OpenBSD: lib_tparm.c,v 1.8 2003/03/17 19:16:59 millert Exp $ */ 2 3 /**************************************************************************** 4 * Copyright (c) 1998,2000 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 * tparm.c 38 * 39 */ 40 41 #include <curses.priv.h> 42 43 #include <ctype.h> 44 #include <term.h> 45 #include <tic.h> 46 47 MODULE_ID("$From: lib_tparm.c,v 1.51 2000/12/10 02:55:08 tom Exp $") 48 49 /* 50 * char * 51 * tparm(string, ...) 52 * 53 * Substitute the given parameters into the given string by the following 54 * rules (taken from terminfo(5)): 55 * 56 * Cursor addressing and other strings requiring parame- 57 * ters in the terminal are described by a parameterized string 58 * capability, with like escapes %x in it. For example, to 59 * address the cursor, the cup capability is given, using two 60 * parameters: the row and column to address to. (Rows and 61 * columns are numbered from zero and refer to the physical 62 * screen visible to the user, not to any unseen memory.) If 63 * the terminal has memory relative cursor addressing, that can 64 * be indicated by 65 * 66 * The parameter mechanism uses a stack and special % 67 * codes to manipulate it. Typically a sequence will push one 68 * of the parameters onto the stack and then print it in some 69 * format. Often more complex operations are necessary. 70 * 71 * The % encodings have the following meanings: 72 * 73 * %% outputs `%' 74 * %c print pop() like %c in printf() 75 * %s print pop() like %s in printf() 76 * %[[:]flags][width[.precision]][doxXs] 77 * as in printf, flags are [-+#] and space 78 * The ':' is used to avoid making %+ or %- 79 * patterns (see below). 80 * 81 * %p[1-9] push ith parm 82 * %P[a-z] set dynamic variable [a-z] to pop() 83 * %g[a-z] get dynamic variable [a-z] and push it 84 * %P[A-Z] set static variable [A-Z] to pop() 85 * %g[A-Z] get static variable [A-Z] and push it 86 * %l push strlen(pop) 87 * %'c' push char constant c 88 * %{nn} push integer constant nn 89 * 90 * %+ %- %* %/ %m 91 * arithmetic (%m is mod): push(pop() op pop()) 92 * %& %| %^ bit operations: push(pop() op pop()) 93 * %= %> %< logical operations: push(pop() op pop()) 94 * %A %O logical and & or operations for conditionals 95 * %! %~ unary operations push(op pop()) 96 * %i add 1 to first two parms (for ANSI terminals) 97 * 98 * %? expr %t thenpart %e elsepart %; 99 * if-then-else, %e elsepart is optional. 100 * else-if's are possible ala Algol 68: 101 * %? c1 %t b1 %e c2 %t b2 %e c3 %t b3 %e c4 %t b4 %e b5 %; 102 * 103 * For those of the above operators which are binary and not commutative, 104 * the stack works in the usual way, with 105 * %gx %gy %m 106 * resulting in x mod y, not the reverse. 107 */ 108 109 #define STACKSIZE 20 110 111 typedef struct { 112 union { 113 unsigned int num; 114 char *str; 115 } data; 116 bool num_type; 117 } stack_frame; 118 119 static stack_frame stack[STACKSIZE]; 120 static int stack_ptr; 121 122 #ifdef TRACE 123 static const char *tname; 124 #endif /* TRACE */ 125 126 static char *out_buff; 127 static size_t out_size; 128 static size_t out_used; 129 130 #if NO_LEAKS 131 NCURSES_EXPORT(void) 132 _nc_free_tparm(void) 133 { 134 if (out_buff != 0) { 135 FreeAndNull(out_buff); 136 out_size = 0; 137 out_used = 0; 138 } 139 } 140 #endif 141 142 static void 143 really_get_space(size_t need) 144 { 145 out_size = need * 2; 146 out_buff = typeRealloc(char, out_size, out_buff); 147 if (out_buff == 0) 148 _nc_err_abort("Out of memory"); 149 } 150 151 static inline void 152 get_space(size_t need) 153 { 154 need += out_used; 155 if (need > out_size) 156 really_get_space(need); 157 } 158 159 static inline void 160 save_text(const char *fmt, const char *s, int len) 161 { 162 size_t s_len = strlen(s); 163 if (len > (int) s_len) 164 s_len = len; 165 166 get_space(s_len + 1); 167 168 (void) snprintf(out_buff + out_used, out_size - out_used, fmt, s); 169 out_used += strlen(out_buff + out_used); 170 } 171 172 static inline void 173 save_number(const char *fmt, int number, int len) 174 { 175 if (len < 30) 176 len = 30; /* actually log10(MAX_INT)+1 */ 177 178 get_space(len + 1); 179 180 (void) snprintf(out_buff + out_used, out_size - out_used, fmt, number); 181 out_used += strlen(out_buff + out_used); 182 } 183 184 static inline void 185 save_char(int c) 186 { 187 if (c == 0) 188 c = 0200; 189 get_space(1); 190 out_buff[out_used++] = c; 191 } 192 193 static inline void 194 npush(int x) 195 { 196 if (stack_ptr < STACKSIZE) { 197 stack[stack_ptr].num_type = TRUE; 198 stack[stack_ptr].data.num = x; 199 stack_ptr++; 200 } 201 } 202 203 static inline int 204 npop(void) 205 { 206 int result = 0; 207 if (stack_ptr > 0) { 208 stack_ptr--; 209 if (stack[stack_ptr].num_type) 210 result = stack[stack_ptr].data.num; 211 } 212 return result; 213 } 214 215 static inline void 216 spush(char *x) 217 { 218 if (stack_ptr < STACKSIZE) { 219 stack[stack_ptr].num_type = FALSE; 220 stack[stack_ptr].data.str = x; 221 stack_ptr++; 222 } 223 } 224 225 static inline char * 226 spop(void) 227 { 228 static char dummy[] = ""; /* avoid const-cast */ 229 char *result = dummy; 230 if (stack_ptr > 0) { 231 stack_ptr--; 232 if (!stack[stack_ptr].num_type && stack[stack_ptr].data.str != 0) 233 result = stack[stack_ptr].data.str; 234 } 235 return result; 236 } 237 238 static inline const char * 239 parse_format(const char *s, char *format, int *len) 240 { 241 bool done = FALSE; 242 bool allowminus = FALSE; 243 bool dot = FALSE; 244 bool err = FALSE; 245 char *fmt = format; 246 int prec = 0; 247 int width = 0; 248 int value = 0; 249 250 *len = 0; 251 *format++ = '%'; 252 while (*s != '\0' && !done) { 253 switch (*s) { 254 case 'c': /* FALLTHRU */ 255 case 'd': /* FALLTHRU */ 256 case 'o': /* FALLTHRU */ 257 case 'x': /* FALLTHRU */ 258 case 'X': /* FALLTHRU */ 259 case 's': 260 *format++ = *s; 261 done = TRUE; 262 break; 263 case '.': 264 *format++ = *s++; 265 if (dot) { 266 err = TRUE; 267 } else { 268 dot = TRUE; 269 prec = value; 270 } 271 value = 0; 272 break; 273 case '#': 274 *format++ = *s++; 275 break; 276 case ' ': 277 *format++ = *s++; 278 break; 279 case ':': 280 s++; 281 allowminus = TRUE; 282 break; 283 case '-': 284 if (allowminus) { 285 *format++ = *s++; 286 } else { 287 done = TRUE; 288 } 289 break; 290 default: 291 if (isdigit(CharOf(*s))) { 292 value = (value * 10) + (*s - '0'); 293 if (value > 10000) 294 err = TRUE; 295 *format++ = *s++; 296 } else { 297 done = TRUE; 298 } 299 } 300 } 301 302 /* 303 * If we found an error, ignore (and remove) the flags. 304 */ 305 if (err) { 306 prec = width = value = 0; 307 format = fmt; 308 *format++ = '%'; 309 *format++ = *s; 310 } 311 312 if (dot) 313 width = value; 314 else 315 prec = value; 316 317 *format = '\0'; 318 /* return maximum string length in print */ 319 *len = (prec > width) ? prec : width; 320 return s; 321 } 322 323 #define isUPPER(c) ((c) >= 'A' && (c) <= 'Z') 324 #define isLOWER(c) ((c) >= 'a' && (c) <= 'z') 325 326 static inline char * 327 tparam_internal(const char *string, va_list ap) 328 { 329 #define NUM_VARS 26 330 char *p_is_s[9]; 331 int param[9]; 332 int lastpop; 333 int popcount; 334 int number; 335 int len; 336 int level; 337 int x, y; 338 int i; 339 size_t len2; 340 register const char *cp; 341 static size_t len_fmt; 342 static char dummy[] = ""; 343 static char *format; 344 static int dynamic_var[NUM_VARS]; 345 static int static_vars[NUM_VARS]; 346 347 out_used = 0; 348 if (string == NULL) 349 return NULL; 350 351 if ((len2 = strlen(string)) > len_fmt) { 352 len_fmt = len2 + len_fmt + 2; 353 if ((format = typeRealloc(char, len_fmt, format)) == 0) 354 return 0; 355 } 356 357 /* 358 * Find the highest parameter-number referred to in the format string. 359 * Use this value to limit the number of arguments copied from the 360 * variable-length argument list. 361 */ 362 363 number = 0; 364 lastpop = -1; 365 popcount = 0; 366 memset(p_is_s, 0, sizeof(p_is_s)); 367 368 /* 369 * Analyze the string to see how many parameters we need from the varargs 370 * list, and what their types are. We will only accept string parameters 371 * if they appear as a %l or %s format following an explicit parameter 372 * reference (e.g., %p2%s). All other parameters are numbers. 373 * 374 * 'number' counts coarsely the number of pop's we see in the string, and 375 * 'popcount' shows the highest parameter number in the string. We would 376 * like to simply use the latter count, but if we are reading termcap 377 * strings, there may be cases that we cannot see the explicit parameter 378 * numbers. 379 */ 380 for (cp = string; (cp - string) < (int) len2;) { 381 if (*cp == '%') { 382 cp++; 383 cp = parse_format(cp, format, &len); 384 switch (*cp) { 385 default: 386 break; 387 388 case 'd': /* FALLTHRU */ 389 case 'o': /* FALLTHRU */ 390 case 'x': /* FALLTHRU */ 391 case 'X': /* FALLTHRU */ 392 case 'c': /* FALLTHRU */ 393 number++; 394 lastpop = -1; 395 break; 396 397 case 'l': 398 case 's': 399 if (lastpop > 0) 400 p_is_s[lastpop - 1] = dummy; 401 ++number; 402 break; 403 404 case 'p': 405 cp++; 406 i = (*cp - '0'); 407 if (i >= 0 && i <= 9) { 408 lastpop = i; 409 if (lastpop > popcount) 410 popcount = lastpop; 411 } 412 break; 413 414 case 'P': 415 case 'g': 416 cp++; 417 break; 418 419 case S_QUOTE: 420 cp += 2; 421 lastpop = -1; 422 break; 423 424 case L_BRACE: 425 cp++; 426 while (*cp >= '0' && *cp <= '9') { 427 cp++; 428 } 429 break; 430 431 case '+': 432 case '-': 433 case '*': 434 case '/': 435 case 'm': 436 case 'A': 437 case 'O': 438 case '&': 439 case '|': 440 case '^': 441 case '=': 442 case '<': 443 case '>': 444 case '!': 445 case '~': 446 lastpop = -1; 447 number += 2; 448 break; 449 450 case 'i': 451 lastpop = -1; 452 if (popcount < 2) 453 popcount = 2; 454 break; 455 } 456 } 457 if (*cp != '\0') 458 cp++; 459 } 460 461 if (number > 9) 462 number = 9; 463 for (i = 0; i < max(popcount, number); i++) { 464 /* 465 * A few caps (such as plab_norm) have string-valued parms. 466 * We'll have to assume that the caller knows the difference, since 467 * a char* and an int may not be the same size on the stack. 468 */ 469 if (p_is_s[i] != 0) { 470 p_is_s[i] = va_arg(ap, char *); 471 } else { 472 param[i] = va_arg(ap, int); 473 } 474 } 475 476 /* 477 * This is a termcap compatibility hack. If there are no explicit pop 478 * operations in the string, load the stack in such a way that 479 * successive pops will grab successive parameters. That will make 480 * the expansion of (for example) \E[%d;%dH work correctly in termcap 481 * style, which means tparam() will expand termcap strings OK. 482 */ 483 stack_ptr = 0; 484 if (popcount == 0) { 485 popcount = number; 486 for (i = number - 1; i >= 0; i--) 487 npush(param[i]); 488 } 489 #ifdef TRACE 490 if (_nc_tracing & TRACE_CALLS) { 491 for (i = 0; i < popcount; i++) { 492 if (p_is_s[i] != 0) 493 save_text(", %s", _nc_visbuf(p_is_s[i]), 0); 494 else 495 save_number(", %d", param[i], 0); 496 } 497 _tracef(T_CALLED("%s(%s%s)"), tname, _nc_visbuf(string), out_buff); 498 out_used = 0; 499 } 500 #endif /* TRACE */ 501 502 while (*string) { 503 if (*string != '%') { 504 save_char(*string); 505 } else { 506 string++; 507 string = parse_format(string, format, &len); 508 switch (*string) { 509 default: 510 break; 511 case '%': 512 save_char('%'); 513 break; 514 515 case 'd': /* FALLTHRU */ 516 case 'o': /* FALLTHRU */ 517 case 'x': /* FALLTHRU */ 518 case 'X': /* FALLTHRU */ 519 case 'c': /* FALLTHRU */ 520 save_number(format, npop(), len); 521 break; 522 523 case 'l': 524 save_number("%d", strlen(spop()), 0); 525 break; 526 527 case 's': 528 save_text(format, spop(), len); 529 break; 530 531 case 'p': 532 string++; 533 i = (*string - '1'); 534 if (i >= 0 && i < 9) { 535 if (p_is_s[i]) 536 spush(p_is_s[i]); 537 else 538 npush(param[i]); 539 } 540 break; 541 542 case 'P': 543 string++; 544 if (isUPPER(*string)) { 545 i = (*string - 'A'); 546 static_vars[i] = npop(); 547 } else if (isLOWER(*string)) { 548 i = (*string - 'a'); 549 dynamic_var[i] = npop(); 550 } 551 break; 552 553 case 'g': 554 string++; 555 if (isUPPER(*string)) { 556 i = (*string - 'A'); 557 npush(static_vars[i]); 558 } else if (isLOWER(*string)) { 559 i = (*string - 'a'); 560 npush(dynamic_var[i]); 561 } 562 break; 563 564 case S_QUOTE: 565 string++; 566 npush(*string); 567 string++; 568 break; 569 570 case L_BRACE: 571 number = 0; 572 string++; 573 while (*string >= '0' && *string <= '9') { 574 number = number * 10 + *string - '0'; 575 string++; 576 } 577 npush(number); 578 break; 579 580 case '+': 581 npush(npop() + npop()); 582 break; 583 584 case '-': 585 y = npop(); 586 x = npop(); 587 npush(x - y); 588 break; 589 590 case '*': 591 npush(npop() * npop()); 592 break; 593 594 case '/': 595 y = npop(); 596 x = npop(); 597 npush(y ? (x / y) : 0); 598 break; 599 600 case 'm': 601 y = npop(); 602 x = npop(); 603 npush(y ? (x % y) : 0); 604 break; 605 606 case 'A': 607 npush(npop() && npop()); 608 break; 609 610 case 'O': 611 npush(npop() || npop()); 612 break; 613 614 case '&': 615 npush(npop() & npop()); 616 break; 617 618 case '|': 619 npush(npop() | npop()); 620 break; 621 622 case '^': 623 npush(npop() ^ npop()); 624 break; 625 626 case '=': 627 y = npop(); 628 x = npop(); 629 npush(x == y); 630 break; 631 632 case '<': 633 y = npop(); 634 x = npop(); 635 npush(x < y); 636 break; 637 638 case '>': 639 y = npop(); 640 x = npop(); 641 npush(x > y); 642 break; 643 644 case '!': 645 npush(!npop()); 646 break; 647 648 case '~': 649 npush(~npop()); 650 break; 651 652 case 'i': 653 if (p_is_s[0] == 0) 654 param[0]++; 655 if (p_is_s[1] == 0) 656 param[1]++; 657 break; 658 659 case '?': 660 break; 661 662 case 't': 663 x = npop(); 664 if (!x) { 665 /* scan forward for %e or %; at level zero */ 666 string++; 667 level = 0; 668 while (*string) { 669 if (*string == '%') { 670 string++; 671 if (*string == '?') 672 level++; 673 else if (*string == ';') { 674 if (level > 0) 675 level--; 676 else 677 break; 678 } else if (*string == 'e' && level == 0) 679 break; 680 } 681 682 if (*string) 683 string++; 684 } 685 } 686 break; 687 688 case 'e': 689 /* scan forward for a %; at level zero */ 690 string++; 691 level = 0; 692 while (*string) { 693 if (*string == '%') { 694 string++; 695 if (*string == '?') 696 level++; 697 else if (*string == ';') { 698 if (level > 0) 699 level--; 700 else 701 break; 702 } 703 } 704 705 if (*string) 706 string++; 707 } 708 break; 709 710 case ';': 711 break; 712 713 } /* endswitch (*string) */ 714 } /* endelse (*string == '%') */ 715 716 if (*string == '\0') 717 break; 718 719 string++; 720 } /* endwhile (*string) */ 721 722 get_space(1); 723 out_buff[out_used] = '\0'; 724 725 T((T_RETURN("%s"), _nc_visbuf(out_buff))); 726 return (out_buff); 727 } 728 729 NCURSES_EXPORT(char *) 730 tparm 731 (NCURSES_CONST char *string,...) 732 { 733 va_list ap; 734 char *result; 735 736 va_start(ap, string); 737 #ifdef TRACE 738 tname = "tparm"; 739 #endif /* TRACE */ 740 result = tparam_internal(string, ap); 741 va_end(ap); 742 return result; 743 } 744