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