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