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