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