1 /**************************************************************************** 2 * Copyright (c) 1998,1999,2000,2001 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 * comp_scan.c --- Lexical scanner for terminfo compiler. 36 * 37 * _nc_reset_input() 38 * _nc_get_token() 39 * _nc_panic_mode() 40 * int _nc_syntax; 41 * int _nc_curr_line; 42 * long _nc_curr_file_pos; 43 * long _nc_comment_start; 44 * long _nc_comment_end; 45 */ 46 47 #include <curses.priv.h> 48 49 #include <ctype.h> 50 #include <term_entry.h> 51 #include <tic.h> 52 53 MODULE_ID("$From: comp_scan.c,v 1.54 2001/02/04 01:09:26 tom Exp $") 54 55 /* 56 * Maximum length of string capability we'll accept before raising an error. 57 * Yes, there is a real capability in /etc/termcap this long, an "is". 58 */ 59 #define MAXCAPLEN 600 60 61 #define iswhite(ch) (ch == ' ' || ch == '\t') 62 63 NCURSES_EXPORT_VAR(int) 64 _nc_syntax = 0; /* termcap or terminfo? */ 65 NCURSES_EXPORT_VAR(long) 66 _nc_curr_file_pos = 0; /* file offset of current line */ 67 NCURSES_EXPORT_VAR(long) 68 _nc_comment_start = 0; /* start of comment range before name */ 69 NCURSES_EXPORT_VAR(long) 70 _nc_comment_end = 0; /* end of comment range before name */ 71 NCURSES_EXPORT_VAR(long) 72 _nc_start_line = 0; /* start line of current entry */ 73 74 NCURSES_EXPORT_VAR(struct token) 75 _nc_curr_token = 76 { 77 0, 0, 0 78 }; 79 80 /***************************************************************************** 81 * 82 * Token-grabbing machinery 83 * 84 *****************************************************************************/ 85 86 static bool first_column; /* See 'next_char()' below */ 87 static char separator; /* capability separator */ 88 static int pushtype; /* type of pushback token */ 89 static char pushname[MAX_NAME_SIZE + 1]; 90 91 #if NCURSES_EXT_FUNCS 92 NCURSES_EXPORT_VAR(bool) 93 _nc_disable_period = FALSE; /* used by tic -a option */ 94 #endif 95 96 static int last_char(void); 97 static int next_char(void); 98 static long stream_pos(void); 99 static bool end_of_stream(void); 100 static void push_back(char c); 101 102 /* Assume we may be looking at a termcap-style continuation */ 103 static inline int 104 eat_escaped_newline(int ch) 105 { 106 if (ch == '\\') 107 while ((ch = next_char()) == '\n' || iswhite(ch)) 108 continue; 109 return ch; 110 } 111 112 /* 113 * int 114 * get_token() 115 * 116 * Scans the input for the next token, storing the specifics in the 117 * global structure 'curr_token' and returning one of the following: 118 * 119 * NAMES A line beginning in column 1. 'name' 120 * will be set to point to everything up to but 121 * not including the first separator on the line. 122 * BOOLEAN An entry consisting of a name followed by 123 * a separator. 'name' will be set to point to 124 * the name of the capability. 125 * NUMBER An entry of the form 126 * name#digits, 127 * 'name' will be set to point to the capability 128 * name and 'valnumber' to the number given. 129 * STRING An entry of the form 130 * name=characters, 131 * 'name' is set to the capability name and 132 * 'valstring' to the string of characters, with 133 * input translations done. 134 * CANCEL An entry of the form 135 * name@, 136 * 'name' is set to the capability name and 137 * 'valnumber' to -1. 138 * EOF The end of the file has been reached. 139 * 140 * A `separator' is either a comma or a semicolon, depending on whether 141 * we are in termcap or terminfo mode. 142 * 143 */ 144 145 NCURSES_EXPORT(int) 146 _nc_get_token(bool silent) 147 { 148 static const char terminfo_punct[] = "@%&*!#"; 149 long number; 150 int type; 151 int ch; 152 char *numchk; 153 char numbuf[80]; 154 unsigned found; 155 static char buffer[MAX_ENTRY_SIZE]; 156 char *ptr; 157 int dot_flag = FALSE; 158 long token_start; 159 160 if (pushtype != NO_PUSHBACK) { 161 int retval = pushtype; 162 163 _nc_set_type(pushname); 164 DEBUG(3, ("pushed-back token: `%s', class %d", 165 _nc_curr_token.tk_name, pushtype)); 166 167 pushtype = NO_PUSHBACK; 168 pushname[0] = '\0'; 169 170 /* currtok wasn't altered by _nc_push_token() */ 171 return (retval); 172 } 173 174 if (end_of_stream()) 175 return (EOF); 176 177 start_token: 178 token_start = stream_pos(); 179 while ((ch = next_char()) == '\n' || iswhite(ch)) 180 continue; 181 182 ch = eat_escaped_newline(ch); 183 184 if (ch == EOF) 185 type = EOF; 186 else { 187 /* if this is a termcap entry, skip a leading separator */ 188 if (separator == ':' && ch == ':') 189 ch = next_char(); 190 191 if (ch == '.' 192 #if NCURSES_EXT_FUNCS 193 && !_nc_disable_period 194 #endif 195 ) { 196 dot_flag = TRUE; 197 DEBUG(8, ("dot-flag set")); 198 199 while ((ch = next_char()) == '.' || iswhite(ch)) 200 continue; 201 } 202 203 if (ch == EOF) { 204 type = EOF; 205 goto end_of_token; 206 } 207 208 /* have to make some punctuation chars legal for terminfo */ 209 if (!isalnum(ch) 210 #if NCURSES_EXT_FUNCS 211 && !(ch == '.' && _nc_disable_period) 212 #endif 213 && !strchr(terminfo_punct, (char) ch)) { 214 if (!silent) 215 _nc_warning("Illegal character (expected alphanumeric or %s) - %s", 216 terminfo_punct, unctrl((chtype) ch)); 217 _nc_panic_mode(separator); 218 goto start_token; 219 } 220 221 ptr = buffer; 222 *(ptr++) = ch; 223 224 if (first_column) { 225 char *desc; 226 227 _nc_comment_start = token_start; 228 _nc_comment_end = _nc_curr_file_pos; 229 _nc_start_line = _nc_curr_line; 230 231 _nc_syntax = ERR; 232 while ((ch = next_char()) != '\n') { 233 if (ch == EOF) 234 _nc_err_abort("premature EOF"); 235 else if (ch == ':' && last_char() != ',') { 236 _nc_syntax = SYN_TERMCAP; 237 separator = ':'; 238 break; 239 } else if (ch == ',') { 240 _nc_syntax = SYN_TERMINFO; 241 separator = ','; 242 /* 243 * Fall-through here is not an accident. The idea is that 244 * if we see a comma, we figure this is terminfo unless we 245 * subsequently run into a colon -- but we don't stop 246 * looking for that colon until hitting a newline. This 247 * allows commas to be embedded in description fields of 248 * either syntax. 249 */ 250 /* FALLTHRU */ 251 } else 252 ch = eat_escaped_newline(ch); 253 254 *ptr++ = ch; 255 } 256 ptr[0] = '\0'; 257 if (_nc_syntax == ERR) { 258 /* 259 * Grrr...what we ought to do here is barf, complaining that 260 * the entry is malformed. But because a couple of name fields 261 * in the 8.2 termcap file end with |\, we just have to assume 262 * it's termcap syntax. 263 */ 264 _nc_syntax = SYN_TERMCAP; 265 separator = ':'; 266 } else if (_nc_syntax == SYN_TERMINFO) { 267 /* throw away trailing /, *$/ */ 268 for (--ptr; iswhite(*ptr) || *ptr == ','; ptr--) 269 continue; 270 ptr[1] = '\0'; 271 } 272 273 /* 274 * This is the soonest we have the terminal name fetched. Set up 275 * for following warning messages. 276 */ 277 ptr = strchr(buffer, '|'); 278 if (ptr == (char *) NULL) 279 ptr = buffer + strlen(buffer); 280 ch = *ptr; 281 *ptr = '\0'; 282 _nc_set_type(buffer); 283 *ptr = ch; 284 285 /* 286 * Compute the boundary between the aliases and the description 287 * field for syntax-checking purposes. 288 */ 289 desc = strrchr(buffer, '|'); 290 if (!silent && desc) { 291 if (*desc == '\0') 292 _nc_warning("empty longname field"); 293 else if (strchr(desc, ' ') == (char *) NULL) 294 _nc_warning("older tic versions may treat the description field as an alias"); 295 } 296 if (!desc) 297 desc = buffer + strlen(buffer); 298 299 /* 300 * Whitespace in a name field other than the long name can confuse 301 * rdist and some termcap tools. Slashes are a no-no. Other 302 * special characters can be dangerous due to shell expansion. 303 */ 304 for (ptr = buffer; ptr < desc; ptr++) { 305 if (isspace(CharOf(*ptr))) { 306 if (!silent) 307 _nc_warning("whitespace in name or alias field"); 308 break; 309 } else if (*ptr == '/') { 310 if (!silent) 311 _nc_warning("slashes aren't allowed in names or aliases"); 312 break; 313 } else if (strchr("$[]!*?", *ptr)) { 314 if (!silent) 315 _nc_warning("dubious character `%c' in name or alias field", *ptr); 316 break; 317 } 318 } 319 320 ptr = buffer; 321 322 _nc_curr_token.tk_name = buffer; 323 type = NAMES; 324 } else { 325 while ((ch = next_char()) != EOF) { 326 if (!isalnum(ch)) { 327 if (_nc_syntax == SYN_TERMINFO) { 328 if (ch != '_') 329 break; 330 } else { /* allow ';' for "k;" */ 331 if (ch != ';') 332 break; 333 } 334 } 335 *(ptr++) = ch; 336 } 337 338 *ptr++ = '\0'; 339 switch (ch) { 340 case ',': 341 case ':': 342 if (ch != separator) 343 _nc_err_abort("Separator inconsistent with syntax"); 344 _nc_curr_token.tk_name = buffer; 345 type = BOOLEAN; 346 break; 347 case '@': 348 if ((ch = next_char()) != separator && !silent) 349 _nc_warning("Missing separator after `%s', have %s", 350 buffer, unctrl((chtype) ch)); 351 _nc_curr_token.tk_name = buffer; 352 type = CANCEL; 353 break; 354 355 case '#': 356 found = 0; 357 while (isalnum(ch = next_char())) { 358 numbuf[found++] = ch; 359 if (found >= sizeof(numbuf) - 1) 360 break; 361 } 362 numbuf[found] = '\0'; 363 number = strtol(numbuf, &numchk, 0); 364 if (!silent) { 365 if (numchk == numbuf) 366 _nc_warning("no value given for `%s'", buffer); 367 if ((*numchk != '\0') || (ch != separator)) 368 _nc_warning("Missing separator"); 369 } 370 _nc_curr_token.tk_name = buffer; 371 _nc_curr_token.tk_valnumber = number; 372 type = NUMBER; 373 break; 374 375 case '=': 376 ch = _nc_trans_string(ptr, buffer + sizeof(buffer)); 377 if (!silent && ch != separator) 378 _nc_warning("Missing separator"); 379 _nc_curr_token.tk_name = buffer; 380 _nc_curr_token.tk_valstring = ptr; 381 type = STRING; 382 break; 383 384 case EOF: 385 type = EOF; 386 break; 387 default: 388 /* just to get rid of the compiler warning */ 389 type = UNDEF; 390 if (!silent) 391 _nc_warning("Illegal character - %s", unctrl((chtype) ch)); 392 } 393 } /* end else (first_column == FALSE) */ 394 } /* end else (ch != EOF) */ 395 396 end_of_token: 397 398 #ifdef TRACE 399 if (dot_flag == TRUE) 400 DEBUG(8, ("Commented out ")); 401 402 if (_nc_tracing >= DEBUG_LEVEL(7)) { 403 switch (type) { 404 case BOOLEAN: 405 _tracef("Token: Boolean; name='%s'", 406 _nc_curr_token.tk_name); 407 break; 408 409 case NUMBER: 410 _tracef("Token: Number; name='%s', value=%d", 411 _nc_curr_token.tk_name, 412 _nc_curr_token.tk_valnumber); 413 break; 414 415 case STRING: 416 _tracef("Token: String; name='%s', value=%s", 417 _nc_curr_token.tk_name, 418 _nc_visbuf(_nc_curr_token.tk_valstring)); 419 break; 420 421 case CANCEL: 422 _tracef("Token: Cancel; name='%s'", 423 _nc_curr_token.tk_name); 424 break; 425 426 case NAMES: 427 428 _tracef("Token: Names; value='%s'", 429 _nc_curr_token.tk_name); 430 break; 431 432 case EOF: 433 _tracef("Token: End of file"); 434 break; 435 436 default: 437 _nc_warning("Bad token type"); 438 } 439 } 440 #endif 441 442 if (dot_flag == TRUE) /* if commented out, use the next one */ 443 type = _nc_get_token(silent); 444 445 DEBUG(3, ("token: `%s', class %d", 446 _nc_curr_token.tk_name != 0 ? _nc_curr_token.tk_name : 447 "<null>", 448 type)); 449 450 return (type); 451 } 452 453 /* 454 * char 455 * trans_string(ptr) 456 * 457 * Reads characters using next_char() until encountering a separator, nl, 458 * or end-of-file. The returned value is the character which caused 459 * reading to stop. The following translations are done on the input: 460 * 461 * ^X goes to ctrl-X (i.e. X & 037) 462 * {\E,\n,\r,\b,\t,\f} go to 463 * {ESCAPE,newline,carriage-return,backspace,tab,formfeed} 464 * {\^,\\} go to {carat,backslash} 465 * \ddd (for ddd = up to three octal digits) goes to the character ddd 466 * 467 * \e == \E 468 * \0 == \200 469 * 470 */ 471 472 NCURSES_EXPORT(char) 473 _nc_trans_string(char *ptr, char *last) 474 { 475 int count = 0; 476 int number = 0; 477 int i, c; 478 chtype ch, last_ch = '\0'; 479 bool ignored = FALSE; 480 bool long_warning = FALSE; 481 482 while ((ch = c = next_char()) != (chtype) separator && c != EOF) { 483 if (ptr == (last - 1)) 484 break; 485 if ((_nc_syntax == SYN_TERMCAP) && c == '\n') 486 break; 487 if (ch == '^' && last_ch != '%') { 488 ch = c = next_char(); 489 if (c == EOF) 490 _nc_err_abort("Premature EOF"); 491 492 if (!(is7bits(ch) && isprint(ch))) { 493 _nc_warning("Illegal ^ character - %s", unctrl(ch)); 494 } 495 if (ch == '?') { 496 *(ptr++) = '\177'; 497 if (_nc_tracing) 498 _nc_warning("Allow ^? as synonym for \\177"); 499 } else { 500 if ((ch &= 037) == 0) 501 ch = 128; 502 *(ptr++) = (char) (ch); 503 } 504 } else if (ch == '\\') { 505 ch = c = next_char(); 506 if (c == EOF) 507 _nc_err_abort("Premature EOF"); 508 509 if (ch >= '0' && ch <= '7') { 510 number = ch - '0'; 511 for (i = 0; i < 2; i++) { 512 ch = c = next_char(); 513 if (c == EOF) 514 _nc_err_abort("Premature EOF"); 515 516 if (c < '0' || c > '7') { 517 if (isdigit(c)) { 518 _nc_warning("Non-octal digit `%c' in \\ sequence", c); 519 /* allow the digit; it'll do less harm */ 520 } else { 521 push_back((char) c); 522 break; 523 } 524 } 525 526 number = number * 8 + c - '0'; 527 } 528 529 if (number == 0) 530 number = 0200; 531 *(ptr++) = (char) number; 532 } else { 533 switch (c) { 534 case 'E': 535 case 'e': 536 *(ptr++) = '\033'; 537 break; 538 539 case 'a': 540 *(ptr++) = '\007'; 541 break; 542 543 case 'l': 544 case 'n': 545 *(ptr++) = '\n'; 546 break; 547 548 case 'r': 549 *(ptr++) = '\r'; 550 break; 551 552 case 'b': 553 *(ptr++) = '\010'; 554 break; 555 556 case 's': 557 *(ptr++) = ' '; 558 break; 559 560 case 'f': 561 *(ptr++) = '\014'; 562 break; 563 564 case 't': 565 *(ptr++) = '\t'; 566 break; 567 568 case '\\': 569 *(ptr++) = '\\'; 570 break; 571 572 case '^': 573 *(ptr++) = '^'; 574 break; 575 576 case ',': 577 *(ptr++) = ','; 578 break; 579 580 case ':': 581 *(ptr++) = ':'; 582 break; 583 584 case '\n': 585 continue; 586 587 default: 588 _nc_warning("Illegal character %s in \\ sequence", 589 unctrl(ch)); 590 *(ptr++) = (char) ch; 591 } /* endswitch (ch) */ 592 } /* endelse (ch < '0' || ch > '7') */ 593 } 594 /* end else if (ch == '\\') */ 595 else if (ch == '\n' && (_nc_syntax == SYN_TERMINFO)) { 596 /* newlines embedded in a terminfo string are ignored */ 597 ignored = TRUE; 598 } else { 599 *(ptr++) = (char) ch; 600 } 601 602 if (!ignored) { 603 last_ch = ch; 604 count++; 605 } 606 ignored = FALSE; 607 608 if (count > MAXCAPLEN && !long_warning) { 609 _nc_warning("Very long string found. Missing separator?"); 610 long_warning = TRUE; 611 } 612 } /* end while */ 613 614 *ptr = '\0'; 615 616 return (ch); 617 } 618 619 /* 620 * _nc_push_token() 621 * 622 * Push a token of given type so that it will be reread by the next 623 * get_token() call. 624 */ 625 626 NCURSES_EXPORT(void) 627 _nc_push_token(int tokclass) 628 { 629 /* 630 * This implementation is kind of bogus, it will fail if we ever do more 631 * than one pushback at a time between get_token() calls. It relies on the 632 * fact that curr_tok is static storage that nothing but get_token() 633 * touches. 634 */ 635 pushtype = tokclass; 636 _nc_get_type(pushname); 637 638 DEBUG(3, ("pushing token: `%s', class %d", 639 _nc_curr_token.tk_name, pushtype)); 640 } 641 642 /* 643 * Panic mode error recovery - skip everything until a "ch" is found. 644 */ 645 NCURSES_EXPORT(void) 646 _nc_panic_mode(char ch) 647 { 648 int c; 649 650 for (;;) { 651 c = next_char(); 652 if (c == ch) 653 return; 654 if (c == EOF) 655 return; 656 } 657 } 658 659 /***************************************************************************** 660 * 661 * Character-stream handling 662 * 663 *****************************************************************************/ 664 665 #define LEXBUFSIZ 1024 666 667 static char *bufptr; /* otherwise, the input buffer pointer */ 668 static char *bufstart; /* start of buffer so we can compute offsets */ 669 static FILE *yyin; /* scanner's input file descriptor */ 670 671 /* 672 * _nc_reset_input() 673 * 674 * Resets the input-reading routines. Used on initialization, 675 * or after a seek has been done. Exactly one argument must be 676 * non-null. 677 */ 678 679 NCURSES_EXPORT(void) 680 _nc_reset_input(FILE * fp, char *buf) 681 { 682 pushtype = NO_PUSHBACK; 683 pushname[0] = '\0'; 684 yyin = fp; 685 bufstart = bufptr = buf; 686 _nc_curr_file_pos = 0L; 687 if (fp != 0) 688 _nc_curr_line = 0; 689 _nc_curr_col = 0; 690 } 691 692 /* 693 * int last_char() 694 * 695 * Returns the final nonblank character on the current input buffer 696 */ 697 static int 698 last_char(void) 699 { 700 size_t len = strlen(bufptr); 701 while (len--) { 702 if (!isspace(CharOf(bufptr[len]))) 703 return bufptr[len]; 704 } 705 return 0; 706 } 707 708 /* 709 * int next_char() 710 * 711 * Returns the next character in the input stream. Comments and leading 712 * white space are stripped. 713 * 714 * The global state variable 'firstcolumn' is set TRUE if the character 715 * returned is from the first column of the input line. 716 * 717 * The global variable _nc_curr_line is incremented for each new line. 718 * The global variable _nc_curr_file_pos is set to the file offset of the 719 * beginning of each line. 720 */ 721 722 static int 723 next_char(void) 724 { 725 if (!yyin) { 726 /* 727 * An string with an embedded null will truncate the input. This is 728 * intentional (we don't read binary files here). 729 */ 730 if (*bufptr == '\0') 731 return (EOF); 732 if (*bufptr == '\n') { 733 _nc_curr_line++; 734 _nc_curr_col = 0; 735 } 736 } else if (!bufptr || !*bufptr) { 737 /* 738 * In theory this could be recoded to do its I/O one character at a 739 * time, saving the buffer space. In practice, this turns out to be 740 * quite hard to get completely right. Try it and see. If you 741 * succeed, don't forget to hack push_back() correspondingly. 742 */ 743 static char *result; 744 static size_t allocated; 745 size_t used; 746 size_t len; 747 748 do { 749 bufstart = 0; 750 used = 0; 751 do { 752 if (used + (LEXBUFSIZ / 4) >= allocated) { 753 allocated += (allocated + LEXBUFSIZ); 754 result = _nc_doalloc(result, allocated); 755 if (result == 0) 756 return (EOF); 757 } 758 if (used == 0) 759 _nc_curr_file_pos = ftell(yyin); 760 761 if (fgets(result + used, allocated - used, yyin) != NULL) { 762 bufstart = result; 763 if (used == 0) { 764 _nc_curr_line++; 765 _nc_curr_col = 0; 766 } 767 } else { 768 if (used != 0) 769 strlcat(result, "\n", allocated); 770 } 771 if ((bufptr = bufstart) != 0) { 772 used = strlen(bufptr); 773 while (iswhite(*bufptr)) 774 bufptr++; 775 776 /* 777 * Treat a trailing <cr><lf> the same as a <newline> so we 778 * can read files on OS/2, etc. 779 */ 780 if ((len = strlen(bufptr)) > 1) { 781 if (bufptr[len - 1] == '\n' 782 && bufptr[len - 2] == '\r') { 783 len--; 784 bufptr[len - 1] = '\n'; 785 bufptr[len] = '\0'; 786 } 787 } 788 } else { 789 return (EOF); 790 } 791 } while (bufptr[len - 1] != '\n'); /* complete a line */ 792 } while (result[0] == '#'); /* ignore comments */ 793 } 794 795 first_column = (bufptr == bufstart); 796 797 _nc_curr_col++; 798 return (*bufptr++); 799 } 800 801 static void 802 push_back(char c) 803 /* push a character back onto the input stream */ 804 { 805 if (bufptr == bufstart) 806 _nc_syserr_abort("Can't backspace off beginning of line"); 807 *--bufptr = c; 808 } 809 810 static long 811 stream_pos(void) 812 /* return our current character position in the input stream */ 813 { 814 return (yyin ? ftell(yyin) : (bufptr ? bufptr - bufstart : 0)); 815 } 816 817 static bool 818 end_of_stream(void) 819 /* are we at end of input? */ 820 { 821 return ((yyin ? feof(yyin) : (bufptr && *bufptr == '\0')) 822 ? TRUE : FALSE); 823 } 824