ncurses 5.7 - patch 20090221
[ncurses.git] / progs / tic.c
1 /****************************************************************************
2  * Copyright (c) 1998-2007,2008 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  *     and: Thomas E. Dickey                        1996 on                 *
33  ****************************************************************************/
34
35 /*
36  *      tic.c --- Main program for terminfo compiler
37  *                      by Eric S. Raymond
38  *
39  */
40
41 #include <progs.priv.h>
42 #include <sys/stat.h>
43
44 #include <dump_entry.h>
45 #include <transform.h>
46
47 MODULE_ID("$Id: tic.c,v 1.137 2008/09/13 16:59:24 tom Exp $")
48
49 const char *_nc_progname = "tic";
50
51 static FILE *log_fp;
52 static FILE *tmp_fp;
53 static bool capdump = FALSE;    /* running as infotocap? */
54 static bool infodump = FALSE;   /* running as captoinfo? */
55 static bool showsummary = FALSE;
56 static const char *to_remove;
57
58 static void (*save_check_termtype) (TERMTYPE *, bool);
59 static void check_termtype(TERMTYPE *tt, bool);
60
61 static const char usage_string[] = "\
62 [-e names] \
63 [-o dir] \
64 [-R name] \
65 [-v[n]] \
66 [-V] \
67 [-w[n]] \
68 [-\
69 1\
70 a\
71 C\
72 c\
73 f\
74 G\
75 g\
76 I\
77 L\
78 N\
79 r\
80 s\
81 T\
82 t\
83 U\
84 x\
85 ] \
86 source-file\n";
87
88 #if NO_LEAKS
89 static void
90 free_namelist(char **src)
91 {
92     if (src != 0) {
93         int n;
94         for (n = 0; src[n] != 0; ++n)
95             free(src[n]);
96         free(src);
97     }
98 }
99 #endif
100
101 static void
102 cleanup(char **namelst GCC_UNUSED)
103 {
104 #if NO_LEAKS
105     free_namelist(namelst);
106 #endif
107     if (tmp_fp != 0)
108         fclose(tmp_fp);
109     if (to_remove != 0) {
110 #if HAVE_REMOVE
111         remove(to_remove);
112 #else
113         unlink(to_remove);
114 #endif
115     }
116 }
117
118 static void
119 failed(const char *msg)
120 {
121     perror(msg);
122     cleanup((char **) 0);
123     ExitProgram(EXIT_FAILURE);
124 }
125
126 static void
127 usage(void)
128 {
129     static const char *const tbl[] =
130     {
131         "Options:",
132         "  -1         format translation output one capability per line",
133 #if NCURSES_XNAMES
134         "  -a         retain commented-out capabilities (sets -x also)",
135 #endif
136         "  -C         translate entries to termcap source form",
137         "  -c         check only, validate input without compiling or translating",
138         "  -e<names>  translate/compile only entries named by comma-separated list",
139         "  -f         format complex strings for readability",
140         "  -G         format %{number} to %'char'",
141         "  -g         format %'char' to %{number}",
142         "  -I         translate entries to terminfo source form",
143         "  -L         translate entries to full terminfo source form",
144         "  -N         disable smart defaults for source translation",
145         "  -o<dir>    set output directory for compiled entry writes",
146         "  -R<name>   restrict translation to given terminfo/termcap version",
147         "  -r         force resolution of all use entries in source translation",
148         "  -s         print summary statistics",
149         "  -T         remove size-restrictions on compiled description",
150 #if NCURSES_XNAMES
151         "  -t         suppress commented-out capabilities",
152 #endif
153         "  -U         suppress post-processing of entries",
154         "  -V         print version",
155         "  -v[n]      set verbosity level",
156         "  -w[n]      set format width for translation output",
157 #if NCURSES_XNAMES
158         "  -x         treat unknown capabilities as user-defined",
159 #endif
160         "",
161         "Parameters:",
162         "  <file>     file to translate or compile"
163     };
164     size_t j;
165
166     fprintf(stderr, "Usage: %s %s\n", _nc_progname, usage_string);
167     for (j = 0; j < SIZEOF(tbl); j++) {
168         fputs(tbl[j], stderr);
169         putc('\n', stderr);
170     }
171     ExitProgram(EXIT_FAILURE);
172 }
173
174 #define L_BRACE '{'
175 #define R_BRACE '}'
176 #define S_QUOTE '\'';
177
178 static void
179 write_it(ENTRY * ep)
180 {
181     unsigned n;
182     int ch;
183     char *s, *d, *t;
184     char result[MAX_ENTRY_SIZE];
185
186     /*
187      * Look for strings that contain %{number}, convert them to %'char',
188      * which is shorter and runs a little faster.
189      */
190     for (n = 0; n < STRCOUNT; n++) {
191         s = ep->tterm.Strings[n];
192         if (VALID_STRING(s)
193             && strchr(s, L_BRACE) != 0) {
194             d = result;
195             t = s;
196             while ((ch = *t++) != 0) {
197                 *d++ = (char) ch;
198                 if (ch == '\\') {
199                     *d++ = *t++;
200                 } else if ((ch == '%')
201                            && (*t == L_BRACE)) {
202                     char *v = 0;
203                     long value = strtol(t + 1, &v, 0);
204                     if (v != 0
205                         && *v == R_BRACE
206                         && value > 0
207                         && value != '\\'        /* FIXME */
208                         && value < 127
209                         && isprint((int) value)) {
210                         *d++ = S_QUOTE;
211                         *d++ = (char) value;
212                         *d++ = S_QUOTE;
213                         t = (v + 1);
214                     }
215                 }
216             }
217             *d = 0;
218             if (strlen(result) < strlen(s))
219                 strcpy(s, result);
220         }
221     }
222
223     _nc_set_type(_nc_first_name(ep->tterm.term_names));
224     _nc_curr_line = ep->startline;
225     _nc_write_entry(&ep->tterm);
226 }
227
228 static bool
229 immedhook(ENTRY * ep GCC_UNUSED)
230 /* write out entries with no use capabilities immediately to save storage */
231 {
232 #if !HAVE_BIG_CORE
233     /*
234      * This is strictly a core-economy kluge.  The really clean way to handle
235      * compilation is to slurp the whole file into core and then do all the
236      * name-collision checks and entry writes in one swell foop.  But the
237      * terminfo master file is large enough that some core-poor systems swap
238      * like crazy when you compile it this way...there have been reports of
239      * this process taking *three hours*, rather than the twenty seconds or
240      * less typical on my development box.
241      *
242      * So.  This hook *immediately* writes out the referenced entry if it
243      * has no use capabilities.  The compiler main loop refrains from
244      * adding the entry to the in-core list when this hook fires.  If some
245      * other entry later needs to reference an entry that got written
246      * immediately, that's OK; the resolution code will fetch it off disk
247      * when it can't find it in core.
248      *
249      * Name collisions will still be detected, just not as cleanly.  The
250      * write_entry() code complains before overwriting an entry that
251      * postdates the time of tic's first call to write_entry().  Thus
252      * it will complain about overwriting entries newly made during the
253      * tic run, but not about overwriting ones that predate it.
254      *
255      * The reason this is a hook, and not in line with the rest of the
256      * compiler code, is that the support for termcap fallback cannot assume
257      * it has anywhere to spool out these entries!
258      *
259      * The _nc_set_type() call here requires a compensating one in
260      * _nc_parse_entry().
261      *
262      * If you define HAVE_BIG_CORE, you'll disable this kluge.  This will
263      * make tic a bit faster (because the resolution code won't have to do
264      * disk I/O nearly as often).
265      */
266     if (ep->nuses == 0) {
267         int oldline = _nc_curr_line;
268
269         write_it(ep);
270         _nc_curr_line = oldline;
271         free(ep->tterm.str_table);
272         return (TRUE);
273     }
274 #endif /* HAVE_BIG_CORE */
275     return (FALSE);
276 }
277
278 static void
279 put_translate(int c)
280 /* emit a comment char, translating terminfo names to termcap names */
281 {
282     static bool in_name = FALSE;
283     static size_t have, used;
284     static char *namebuf, *suffix;
285
286     if (in_name) {
287         if (used + 1 >= have) {
288             have += 132;
289             namebuf = typeRealloc(char, have, namebuf);
290             suffix = typeRealloc(char, have, suffix);
291         }
292         if (c == '\n' || c == '@') {
293             namebuf[used++] = '\0';
294             (void) putchar('<');
295             (void) fputs(namebuf, stdout);
296             putchar(c);
297             in_name = FALSE;
298         } else if (c != '>') {
299             namebuf[used++] = (char) c;
300         } else {                /* ah! candidate name! */
301             char *up;
302             NCURSES_CONST char *tp;
303
304             namebuf[used++] = '\0';
305             in_name = FALSE;
306
307             suffix[0] = '\0';
308             if ((up = strchr(namebuf, '#')) != 0
309                 || (up = strchr(namebuf, '=')) != 0
310                 || ((up = strchr(namebuf, '@')) != 0 && up[1] == '>')) {
311                 (void) strcpy(suffix, up);
312                 *up = '\0';
313             }
314
315             if ((tp = nametrans(namebuf)) != 0) {
316                 (void) putchar(':');
317                 (void) fputs(tp, stdout);
318                 (void) fputs(suffix, stdout);
319                 (void) putchar(':');
320             } else {
321                 /* couldn't find a translation, just dump the name */
322                 (void) putchar('<');
323                 (void) fputs(namebuf, stdout);
324                 (void) fputs(suffix, stdout);
325                 (void) putchar('>');
326             }
327         }
328     } else {
329         used = 0;
330         if (c == '<') {
331             in_name = TRUE;
332         } else {
333             putchar(c);
334         }
335     }
336 }
337
338 /* Returns a string, stripped of leading/trailing whitespace */
339 static char *
340 stripped(char *src)
341 {
342     while (isspace(UChar(*src)))
343         src++;
344     if (*src != '\0') {
345         char *dst = strcpy((char *) malloc(strlen(src) + 1), src);
346         size_t len = strlen(dst);
347         while (--len != 0 && isspace(UChar(dst[len])))
348             dst[len] = '\0';
349         return dst;
350     }
351     return 0;
352 }
353
354 static FILE *
355 open_input(const char *filename)
356 {
357     FILE *fp = fopen(filename, "r");
358     struct stat sb;
359
360     if (fp == 0) {
361         fprintf(stderr, "%s: Can't open %s\n", _nc_progname, filename);
362         ExitProgram(EXIT_FAILURE);
363     }
364     if (fstat(fileno(fp), &sb) < 0
365         || (sb.st_mode & S_IFMT) != S_IFREG) {
366         fprintf(stderr, "%s: %s is not a file\n", _nc_progname, filename);
367         ExitProgram(EXIT_FAILURE);
368     }
369     return fp;
370 }
371
372 /* Parse the "-e" option-value into a list of names */
373 static char **
374 make_namelist(char *src)
375 {
376     char **dst = 0;
377
378     char *s, *base;
379     unsigned pass, n, nn;
380     char buffer[BUFSIZ];
381
382     if (src == 0) {
383         /* EMPTY */ ;
384     } else if (strchr(src, '/') != 0) {         /* a filename */
385         FILE *fp = open_input(src);
386
387         for (pass = 1; pass <= 2; pass++) {
388             nn = 0;
389             while (fgets(buffer, sizeof(buffer), fp) != 0) {
390                 if ((s = stripped(buffer)) != 0) {
391                     if (dst != 0)
392                         dst[nn] = s;
393                     else
394                         free(s);
395                     nn++;
396                 }
397             }
398             if (pass == 1) {
399                 dst = typeCalloc(char *, nn + 1);
400                 rewind(fp);
401             }
402         }
403         fclose(fp);
404     } else {                    /* literal list of names */
405         for (pass = 1; pass <= 2; pass++) {
406             for (n = nn = 0, base = src;; n++) {
407                 int mark = src[n];
408                 if (mark == ',' || mark == '\0') {
409                     if (pass == 1) {
410                         nn++;
411                     } else {
412                         src[n] = '\0';
413                         if ((s = stripped(base)) != 0)
414                             dst[nn++] = s;
415                         base = &src[n + 1];
416                     }
417                 }
418                 if (mark == '\0')
419                     break;
420             }
421             if (pass == 1)
422                 dst = typeCalloc(char *, nn + 1);
423         }
424     }
425     if (showsummary && (dst != 0)) {
426         fprintf(log_fp, "Entries that will be compiled:\n");
427         for (n = 0; dst[n] != 0; n++)
428             fprintf(log_fp, "%u:%s\n", n + 1, dst[n]);
429     }
430     return dst;
431 }
432
433 static bool
434 matches(char **needle, const char *haystack)
435 /* does entry in needle list match |-separated field in haystack? */
436 {
437     bool code = FALSE;
438     size_t n;
439
440     if (needle != 0) {
441         for (n = 0; needle[n] != 0; n++) {
442             if (_nc_name_match(haystack, needle[n], "|")) {
443                 code = TRUE;
444                 break;
445             }
446         }
447     } else
448         code = TRUE;
449     return (code);
450 }
451
452 static FILE *
453 open_tempfile(char *name)
454 {
455     FILE *result = 0;
456 #if HAVE_MKSTEMP
457     int fd = mkstemp(name);
458     if (fd >= 0)
459         result = fdopen(fd, "w");
460 #else
461     if (tmpnam(name) != 0)
462         result = fopen(name, "w");
463 #endif
464     return result;
465 }
466
467 int
468 main(int argc, char *argv[])
469 {
470     char my_tmpname[PATH_MAX];
471     int v_opt = -1, debug_level;
472     int smart_defaults = TRUE;
473     char *termcap;
474     ENTRY *qp;
475
476     int this_opt, last_opt = '?';
477
478     int outform = F_TERMINFO;   /* output format */
479     int sortmode = S_TERMINFO;  /* sort_mode */
480
481     int width = 60;
482     bool formatted = FALSE;     /* reformat complex strings? */
483     bool literal = FALSE;       /* suppress post-processing? */
484     int numbers = 0;            /* format "%'char'" to/from "%{number}" */
485     bool forceresolve = FALSE;  /* force resolution */
486     bool limited = TRUE;
487     char *tversion = (char *) NULL;
488     const char *source_file = "terminfo";
489     char **namelst = 0;
490     char *outdir = (char *) NULL;
491     bool check_only = FALSE;
492     bool suppress_untranslatable = FALSE;
493
494     log_fp = stderr;
495
496     _nc_progname = _nc_rootname(argv[0]);
497
498     if ((infodump = (strcmp(_nc_progname, PROG_CAPTOINFO) == 0)) != FALSE) {
499         outform = F_TERMINFO;
500         sortmode = S_TERMINFO;
501     }
502     if ((capdump = (strcmp(_nc_progname, PROG_INFOTOCAP) == 0)) != FALSE) {
503         outform = F_TERMCAP;
504         sortmode = S_TERMCAP;
505     }
506 #if NCURSES_XNAMES
507     use_extended_names(FALSE);
508 #endif
509
510     /*
511      * Processing arguments is a little complicated, since someone made a
512      * design decision to allow the numeric values for -w, -v options to
513      * be optional.
514      */
515     while ((this_opt = getopt(argc, argv,
516                               "0123456789CILNR:TUVace:fGgo:rstvwx")) != -1) {
517         if (isdigit(this_opt)) {
518             switch (last_opt) {
519             case 'v':
520                 v_opt = (v_opt * 10) + (this_opt - '0');
521                 break;
522             case 'w':
523                 width = (width * 10) + (this_opt - '0');
524                 break;
525             default:
526                 if (this_opt != '1')
527                     usage();
528                 last_opt = this_opt;
529                 width = 0;
530             }
531             continue;
532         }
533         switch (this_opt) {
534         case 'C':
535             capdump = TRUE;
536             outform = F_TERMCAP;
537             sortmode = S_TERMCAP;
538             break;
539         case 'I':
540             infodump = TRUE;
541             outform = F_TERMINFO;
542             sortmode = S_TERMINFO;
543             break;
544         case 'L':
545             infodump = TRUE;
546             outform = F_VARIABLE;
547             sortmode = S_VARIABLE;
548             break;
549         case 'N':
550             smart_defaults = FALSE;
551             literal = TRUE;
552             break;
553         case 'R':
554             tversion = optarg;
555             break;
556         case 'T':
557             limited = FALSE;
558             break;
559         case 'U':
560             literal = TRUE;
561             break;
562         case 'V':
563             puts(curses_version());
564             cleanup(namelst);
565             ExitProgram(EXIT_SUCCESS);
566         case 'c':
567             check_only = TRUE;
568             break;
569         case 'e':
570             namelst = make_namelist(optarg);
571             break;
572         case 'f':
573             formatted = TRUE;
574             break;
575         case 'G':
576             numbers = 1;
577             break;
578         case 'g':
579             numbers = -1;
580             break;
581         case 'o':
582             outdir = optarg;
583             break;
584         case 'r':
585             forceresolve = TRUE;
586             break;
587         case 's':
588             showsummary = TRUE;
589             break;
590         case 'v':
591             v_opt = 0;
592             break;
593         case 'w':
594             width = 0;
595             break;
596 #if NCURSES_XNAMES
597         case 't':
598             _nc_disable_period = FALSE;
599             suppress_untranslatable = TRUE;
600             break;
601         case 'a':
602             _nc_disable_period = TRUE;
603             /* FALLTHRU */
604         case 'x':
605             use_extended_names(TRUE);
606             break;
607 #endif
608         default:
609             usage();
610         }
611         last_opt = this_opt;
612     }
613
614     debug_level = (v_opt > 0) ? v_opt : (v_opt == 0);
615     set_trace_level(debug_level);
616
617     if (_nc_tracing) {
618         save_check_termtype = _nc_check_termtype2;
619         _nc_check_termtype2 = check_termtype;
620     }
621 #if !HAVE_BIG_CORE
622     /*
623      * Aaargh! immedhook seriously hoses us!
624      *
625      * One problem with immedhook is it means we can't do -e.  Problem
626      * is that we can't guarantee that for each terminal listed, all the
627      * terminals it depends on will have been kept in core for reference
628      * resolution -- in fact it's certain the primitive types at the end
629      * of reference chains *won't* be in core unless they were explicitly
630      * in the select list themselves.
631      */
632     if (namelst && (!infodump && !capdump)) {
633         (void) fprintf(stderr,
634                        "Sorry, -e can't be used without -I or -C\n");
635         cleanup(namelst);
636         ExitProgram(EXIT_FAILURE);
637     }
638 #endif /* HAVE_BIG_CORE */
639
640     if (optind < argc) {
641         source_file = argv[optind++];
642         if (optind < argc) {
643             fprintf(stderr,
644                     "%s: Too many file names.  Usage:\n\t%s %s",
645                     _nc_progname,
646                     _nc_progname,
647                     usage_string);
648             ExitProgram(EXIT_FAILURE);
649         }
650     } else {
651         if (infodump == TRUE) {
652             /* captoinfo's no-argument case */
653             source_file = "/etc/termcap";
654             if ((termcap = getenv("TERMCAP")) != 0
655                 && (namelst = make_namelist(getenv("TERM"))) != 0) {
656                 if (access(termcap, F_OK) == 0) {
657                     /* file exists */
658                     source_file = termcap;
659                 } else if ((tmp_fp = open_tempfile(strcpy(my_tmpname,
660                                                           "/tmp/XXXXXX")))
661                            != 0) {
662                     source_file = my_tmpname;
663                     fprintf(tmp_fp, "%s\n", termcap);
664                     fclose(tmp_fp);
665                     tmp_fp = open_input(source_file);
666                     to_remove = source_file;
667                 } else {
668                     failed("tmpnam");
669                 }
670             }
671         } else {
672             /* tic */
673             fprintf(stderr,
674                     "%s: File name needed.  Usage:\n\t%s %s",
675                     _nc_progname,
676                     _nc_progname,
677                     usage_string);
678             cleanup(namelst);
679             ExitProgram(EXIT_FAILURE);
680         }
681     }
682
683     if (tmp_fp == 0)
684         tmp_fp = open_input(source_file);
685
686     if (infodump)
687         dump_init(tversion,
688                   smart_defaults
689                   ? outform
690                   : F_LITERAL,
691                   sortmode, width, debug_level, formatted);
692     else if (capdump)
693         dump_init(tversion,
694                   outform,
695                   sortmode, width, debug_level, FALSE);
696
697     /* parse entries out of the source file */
698     _nc_set_source(source_file);
699 #if !HAVE_BIG_CORE
700     if (!(check_only || infodump || capdump))
701         _nc_set_writedir(outdir);
702 #endif /* HAVE_BIG_CORE */
703     _nc_read_entry_source(tmp_fp, (char *) NULL,
704                           !smart_defaults || literal, FALSE,
705                           ((check_only || infodump || capdump)
706                            ? NULLHOOK
707                            : immedhook));
708
709     /* do use resolution */
710     if (check_only || (!infodump && !capdump) || forceresolve) {
711         if (!_nc_resolve_uses2(TRUE, literal) && !check_only) {
712             cleanup(namelst);
713             ExitProgram(EXIT_FAILURE);
714         }
715     }
716
717     /* length check */
718     if (check_only && (capdump || infodump)) {
719         for_entry_list(qp) {
720             if (matches(namelst, qp->tterm.term_names)) {
721                 int len = fmt_entry(&qp->tterm, NULL, FALSE, TRUE, infodump, numbers);
722
723                 if (len > (infodump ? MAX_TERMINFO_LENGTH : MAX_TERMCAP_LENGTH))
724                     (void) fprintf(stderr,
725                                    "warning: resolved %s entry is %d bytes long\n",
726                                    _nc_first_name(qp->tterm.term_names),
727                                    len);
728             }
729         }
730     }
731
732     /* write or dump all entries */
733     if (!check_only) {
734         if (!infodump && !capdump) {
735             _nc_set_writedir(outdir);
736             for_entry_list(qp) {
737                 if (matches(namelst, qp->tterm.term_names))
738                     write_it(qp);
739             }
740         } else {
741             /* this is in case infotocap() generates warnings */
742             _nc_curr_col = _nc_curr_line = -1;
743
744             for_entry_list(qp) {
745                 if (matches(namelst, qp->tterm.term_names)) {
746                     int j = qp->cend - qp->cstart;
747                     int len = 0;
748
749                     /* this is in case infotocap() generates warnings */
750                     _nc_set_type(_nc_first_name(qp->tterm.term_names));
751
752                     (void) fseek(tmp_fp, qp->cstart, SEEK_SET);
753                     while (j-- > 0) {
754                         if (infodump)
755                             (void) putchar(fgetc(tmp_fp));
756                         else
757                             put_translate(fgetc(tmp_fp));
758                     }
759
760                     dump_entry(&qp->tterm, suppress_untranslatable,
761                                limited, numbers, NULL);
762                     for (j = 0; j < (int) qp->nuses; j++)
763                         dump_uses(qp->uses[j].name, !capdump);
764                     len = show_entry();
765                     if (debug_level != 0 && !limited)
766                         printf("# length=%d\n", len);
767                 }
768             }
769             if (!namelst && _nc_tail) {
770                 int c, oldc = '\0';
771                 bool in_comment = FALSE;
772                 bool trailing_comment = FALSE;
773
774                 (void) fseek(tmp_fp, _nc_tail->cend, SEEK_SET);
775                 while ((c = fgetc(tmp_fp)) != EOF) {
776                     if (oldc == '\n') {
777                         if (c == '#') {
778                             trailing_comment = TRUE;
779                             in_comment = TRUE;
780                         } else {
781                             in_comment = FALSE;
782                         }
783                     }
784                     if (trailing_comment
785                         && (in_comment || (oldc == '\n' && c == '\n')))
786                         putchar(c);
787                     oldc = c;
788                 }
789             }
790         }
791     }
792
793     /* Show the directory into which entries were written, and the total
794      * number of entries
795      */
796     if (showsummary
797         && (!(check_only || infodump || capdump))) {
798         int total = _nc_tic_written();
799         if (total != 0)
800             fprintf(log_fp, "%d entries written to %s\n",
801                     total,
802                     _nc_tic_dir((char *) 0));
803         else
804             fprintf(log_fp, "No entries written\n");
805     }
806     cleanup(namelst);
807     ExitProgram(EXIT_SUCCESS);
808 }
809
810 /*
811  * This bit of legerdemain turns all the terminfo variable names into
812  * references to locations in the arrays Booleans, Numbers, and Strings ---
813  * precisely what's needed (see comp_parse.c).
814  */
815 #undef CUR
816 #define CUR tp->
817
818 /*
819  * Check if the alternate character-set capabilities are consistent.
820  */
821 static void
822 check_acs(TERMTYPE *tp)
823 {
824     if (VALID_STRING(acs_chars)) {
825         const char *boxes = "lmkjtuvwqxn";
826         char mapped[256];
827         char missing[256];
828         const char *p;
829         char *q;
830
831         memset(mapped, 0, sizeof(mapped));
832         for (p = acs_chars; *p != '\0'; p += 2) {
833             if (p[1] == '\0') {
834                 _nc_warning("acsc has odd number of characters");
835                 break;
836             }
837             mapped[UChar(p[0])] = p[1];
838         }
839
840         if (mapped[UChar('I')] && !mapped[UChar('i')]) {
841             _nc_warning("acsc refers to 'I', which is probably an error");
842         }
843
844         for (p = boxes, q = missing; *p != '\0'; ++p) {
845             if (!mapped[UChar(p[0])]) {
846                 *q++ = p[0];
847             }
848         }
849         *q = '\0';
850
851         assert(strlen(missing) <= strlen(boxes));
852         if (*missing != '\0' && strcmp(missing, boxes)) {
853             _nc_warning("acsc is missing some line-drawing mapping: %s", missing);
854         }
855     }
856 }
857
858 /*
859  * Check if the color capabilities are consistent
860  */
861 static void
862 check_colors(TERMTYPE *tp)
863 {
864     if ((max_colors > 0) != (max_pairs > 0)
865         || ((max_colors > max_pairs) && (initialize_pair == 0)))
866         _nc_warning("inconsistent values for max_colors (%d) and max_pairs (%d)",
867                     max_colors, max_pairs);
868
869     PAIRED(set_foreground, set_background);
870     PAIRED(set_a_foreground, set_a_background);
871     PAIRED(set_color_pair, initialize_pair);
872
873     if (VALID_STRING(set_foreground)
874         && VALID_STRING(set_a_foreground)
875         && !_nc_capcmp(set_foreground, set_a_foreground))
876         _nc_warning("expected setf/setaf to be different");
877
878     if (VALID_STRING(set_background)
879         && VALID_STRING(set_a_background)
880         && !_nc_capcmp(set_background, set_a_background))
881         _nc_warning("expected setb/setab to be different");
882
883     /* see: has_colors() */
884     if (VALID_NUMERIC(max_colors) && VALID_NUMERIC(max_pairs)
885         && (((set_foreground != NULL)
886              && (set_background != NULL))
887             || ((set_a_foreground != NULL)
888                 && (set_a_background != NULL))
889             || set_color_pair)) {
890         if (!VALID_STRING(orig_pair) && !VALID_STRING(orig_colors))
891             _nc_warning("expected either op/oc string for resetting colors");
892     }
893 }
894
895 static char
896 keypad_final(const char *string)
897 {
898     char result = '\0';
899
900     if (VALID_STRING(string)
901         && *string++ == '\033'
902         && *string++ == 'O'
903         && strlen(string) == 1) {
904         result = *string;
905     }
906
907     return result;
908 }
909
910 static int
911 keypad_index(const char *string)
912 {
913     char *test;
914     const char *list = "PQRSwxymtuvlqrsPpn";    /* app-keypad except "Enter" */
915     int ch;
916     int result = -1;
917
918     if ((ch = keypad_final(string)) != '\0') {
919         test = strchr(list, ch);
920         if (test != 0)
921             result = (test - list);
922     }
923     return result;
924 }
925
926 #define MAX_KP 5
927 /*
928  * Do a quick sanity-check for vt100-style keypads to see if the 5-key keypad
929  * is mapped inconsistently.
930  */
931 static void
932 check_keypad(TERMTYPE *tp)
933 {
934     char show[80];
935
936     if (VALID_STRING(key_a1) &&
937         VALID_STRING(key_a3) &&
938         VALID_STRING(key_b2) &&
939         VALID_STRING(key_c1) &&
940         VALID_STRING(key_c3)) {
941         char final[MAX_KP + 1];
942         int list[MAX_KP];
943         int increase = 0;
944         int j, k, kk;
945         int last;
946         int test;
947
948         final[0] = keypad_final(key_a1);
949         final[1] = keypad_final(key_a3);
950         final[2] = keypad_final(key_b2);
951         final[3] = keypad_final(key_c1);
952         final[4] = keypad_final(key_c3);
953         final[5] = '\0';
954
955         /* special case: legacy coding using 1,2,3,0,. on the bottom */
956         assert(strlen(final) <= MAX_KP);
957         if (!strcmp(final, "qsrpn"))
958             return;
959
960         list[0] = keypad_index(key_a1);
961         list[1] = keypad_index(key_a3);
962         list[2] = keypad_index(key_b2);
963         list[3] = keypad_index(key_c1);
964         list[4] = keypad_index(key_c3);
965
966         /* check that they're all vt100 keys */
967         for (j = 0; j < MAX_KP; ++j) {
968             if (list[j] < 0) {
969                 return;
970             }
971         }
972
973         /* check if they're all in increasing order */
974         for (j = 1; j < MAX_KP; ++j) {
975             if (list[j] > list[j - 1]) {
976                 ++increase;
977             }
978         }
979         if (increase != (MAX_KP - 1)) {
980             show[0] = '\0';
981
982             for (j = 0, last = -1; j < MAX_KP; ++j) {
983                 for (k = 0, kk = -1, test = 100; k < 5; ++k) {
984                     if (list[k] > last &&
985                         list[k] < test) {
986                         test = list[k];
987                         kk = k;
988                     }
989                 }
990                 last = test;
991                 assert(strlen(show) < (MAX_KP * 4));
992                 switch (kk) {
993                 case 0:
994                     strcat(show, " ka1");
995                     break;
996                 case 1:
997                     strcat(show, " ka3");
998                     break;
999                 case 2:
1000                     strcat(show, " kb2");
1001                     break;
1002                 case 3:
1003                     strcat(show, " kc1");
1004                     break;
1005                 case 4:
1006                     strcat(show, " kc3");
1007                     break;
1008                 }
1009             }
1010
1011             _nc_warning("vt100 keypad order inconsistent: %s", show);
1012         }
1013
1014     } else if (VALID_STRING(key_a1) ||
1015                VALID_STRING(key_a3) ||
1016                VALID_STRING(key_b2) ||
1017                VALID_STRING(key_c1) ||
1018                VALID_STRING(key_c3)) {
1019         show[0] = '\0';
1020         if (keypad_index(key_a1) >= 0)
1021             strcat(show, " ka1");
1022         if (keypad_index(key_a3) >= 0)
1023             strcat(show, " ka3");
1024         if (keypad_index(key_b2) >= 0)
1025             strcat(show, " kb2");
1026         if (keypad_index(key_c1) >= 0)
1027             strcat(show, " kc1");
1028         if (keypad_index(key_c3) >= 0)
1029             strcat(show, " kc3");
1030         if (*show != '\0')
1031             _nc_warning("vt100 keypad map incomplete:%s", show);
1032     }
1033 }
1034
1035 /*
1036  * Returns the expected number of parameters for the given capability.
1037  */
1038 static int
1039 expected_params(const char *name)
1040 {
1041     /* *INDENT-OFF* */
1042     static const struct {
1043         const char *name;
1044         int count;
1045     } table[] = {
1046         { "S0",                 1 },    /* 'screen' extension */
1047         { "birep",              2 },
1048         { "chr",                1 },
1049         { "colornm",            1 },
1050         { "cpi",                1 },
1051         { "csnm",               1 },
1052         { "csr",                2 },
1053         { "cub",                1 },
1054         { "cud",                1 },
1055         { "cuf",                1 },
1056         { "cup",                2 },
1057         { "cuu",                1 },
1058         { "cvr",                1 },
1059         { "cwin",               5 },
1060         { "dch",                1 },
1061         { "defc",               3 },
1062         { "dial",               1 },
1063         { "dispc",              1 },
1064         { "dl",                 1 },
1065         { "ech",                1 },
1066         { "getm",               1 },
1067         { "hpa",                1 },
1068         { "ich",                1 },
1069         { "il",                 1 },
1070         { "indn",               1 },
1071         { "initc",              4 },
1072         { "initp",              7 },
1073         { "lpi",                1 },
1074         { "mc5p",               1 },
1075         { "mrcup",              2 },
1076         { "mvpa",               1 },
1077         { "pfkey",              2 },
1078         { "pfloc",              2 },
1079         { "pfx",                2 },
1080         { "pfxl",               3 },
1081         { "pln",                2 },
1082         { "qdial",              1 },
1083         { "rcsd",               1 },
1084         { "rep",                2 },
1085         { "rin",                1 },
1086         { "sclk",               3 },
1087         { "scp",                1 },
1088         { "scs",                1 },
1089         { "scsd",               2 },
1090         { "setab",              1 },
1091         { "setaf",              1 },
1092         { "setb",               1 },
1093         { "setcolor",           1 },
1094         { "setf",               1 },
1095         { "sgr",                9 },
1096         { "sgr1",               6 },
1097         { "slength",            1 },
1098         { "slines",             1 },
1099         { "smgbp",              1 },    /* 2 if smgtp is not given */
1100         { "smglp",              1 },
1101         { "smglr",              2 },
1102         { "smgrp",              1 },
1103         { "smgtb",              2 },
1104         { "smgtp",              1 },
1105         { "tsl",                1 },
1106         { "u6",                 -1 },
1107         { "vpa",                1 },
1108         { "wind",               4 },
1109         { "wingo",              1 },
1110     };
1111     /* *INDENT-ON* */
1112
1113     unsigned n;
1114     int result = 0;             /* function-keys, etc., use none */
1115
1116     for (n = 0; n < SIZEOF(table); n++) {
1117         if (!strcmp(name, table[n].name)) {
1118             result = table[n].count;
1119             break;
1120         }
1121     }
1122
1123     return result;
1124 }
1125
1126 /*
1127  * Make a quick sanity check for the parameters which are used in the given
1128  * strings.  If there are no "%p" tokens, then there should be no other "%"
1129  * markers.
1130  */
1131 static void
1132 check_params(TERMTYPE *tp, const char *name, char *value)
1133 {
1134     int expected = expected_params(name);
1135     int actual = 0;
1136     int n;
1137     bool params[10];
1138     char *s = value;
1139
1140 #ifdef set_top_margin_parm
1141     if (!strcmp(name, "smgbp")
1142         && set_top_margin_parm == 0)
1143         expected = 2;
1144 #endif
1145
1146     for (n = 0; n < 10; n++)
1147         params[n] = FALSE;
1148
1149     while (*s != 0) {
1150         if (*s == '%') {
1151             if (*++s == '\0') {
1152                 _nc_warning("expected character after %% in %s", name);
1153                 break;
1154             } else if (*s == 'p') {
1155                 if (*++s == '\0' || !isdigit((int) *s)) {
1156                     _nc_warning("expected digit after %%p in %s", name);
1157                     return;
1158                 } else {
1159                     n = (*s - '0');
1160                     if (n > actual)
1161                         actual = n;
1162                     params[n] = TRUE;
1163                 }
1164             }
1165         }
1166         s++;
1167     }
1168
1169     if (params[0]) {
1170         _nc_warning("%s refers to parameter 0 (%%p0), which is not allowed", name);
1171     }
1172     if (value == set_attributes || expected < 0) {
1173         ;
1174     } else if (expected != actual) {
1175         _nc_warning("%s uses %d parameters, expected %d", name,
1176                     actual, expected);
1177         for (n = 1; n < actual; n++) {
1178             if (!params[n])
1179                 _nc_warning("%s omits parameter %d", name, n);
1180         }
1181     }
1182 }
1183
1184 static char *
1185 skip_delay(char *s)
1186 {
1187     while (*s == '/' || isdigit(UChar(*s)))
1188         ++s;
1189     return s;
1190 }
1191
1192 /*
1193  * Skip a delay altogether, e.g., when comparing a simple string to sgr,
1194  * the latter may have a worst-case delay on the end.
1195  */
1196 static char *
1197 ignore_delays(char *s)
1198 {
1199     int delaying = 0;
1200
1201     do {
1202         switch (*s) {
1203         case '$':
1204             if (delaying == 0)
1205                 delaying = 1;
1206             break;
1207         case '<':
1208             if (delaying == 1)
1209                 delaying = 2;
1210             break;
1211         case '\0':
1212             delaying = 0;
1213             break;
1214         default:
1215             if (delaying) {
1216                 s = skip_delay(s);
1217                 if (*s == '>')
1218                     ++s;
1219                 delaying = 0;
1220             }
1221             break;
1222         }
1223         if (delaying)
1224             ++s;
1225     } while (delaying);
1226     return s;
1227 }
1228
1229 /*
1230  * An sgr string may contain several settings other than the one we're
1231  * interested in, essentially sgr0 + rmacs + whatever.  As long as the
1232  * "whatever" is contained in the sgr string, that is close enough for our
1233  * sanity check.
1234  */
1235 static bool
1236 similar_sgr(int num, char *a, char *b)
1237 {
1238     static const char *names[] =
1239     {
1240         "none"
1241         ,"standout"
1242         ,"underline"
1243         ,"reverse"
1244         ,"blink"
1245         ,"dim"
1246         ,"bold"
1247         ,"invis"
1248         ,"protect"
1249         ,"altcharset"
1250     };
1251     char *base_a = a;
1252     char *base_b = b;
1253     int delaying = 0;
1254
1255     while (*b != 0) {
1256         while (*a != *b) {
1257             if (*a == 0) {
1258                 if (b[0] == '$'
1259                     && b[1] == '<') {
1260                     _nc_warning("Did not find delay %s", _nc_visbuf(b));
1261                 } else {
1262                     _nc_warning("checking sgr(%s) %s\n\tcompare to %s\n\tunmatched %s",
1263                                 names[num], _nc_visbuf2(1, base_a),
1264                                 _nc_visbuf2(2, base_b),
1265                                 _nc_visbuf2(3, b));
1266                 }
1267                 return FALSE;
1268             } else if (delaying) {
1269                 a = skip_delay(a);
1270                 b = skip_delay(b);
1271             } else {
1272                 a++;
1273             }
1274         }
1275         switch (*a) {
1276         case '$':
1277             if (delaying == 0)
1278                 delaying = 1;
1279             break;
1280         case '<':
1281             if (delaying == 1)
1282                 delaying = 2;
1283             break;
1284         default:
1285             delaying = 0;
1286             break;
1287         }
1288         a++;
1289         b++;
1290     }
1291     /* ignore delays on the end of the string */
1292     a = ignore_delays(a);
1293     return ((num != 0) || (*a == 0));
1294 }
1295
1296 static char *
1297 check_sgr(TERMTYPE *tp, char *zero, int num, char *cap, const char *name)
1298 {
1299     char *test;
1300
1301     _nc_tparm_err = 0;
1302     test = TPARM_9(set_attributes,
1303                    num == 1,
1304                    num == 2,
1305                    num == 3,
1306                    num == 4,
1307                    num == 5,
1308                    num == 6,
1309                    num == 7,
1310                    num == 8,
1311                    num == 9);
1312     if (test != 0) {
1313         if (PRESENT(cap)) {
1314             if (!similar_sgr(num, test, cap)) {
1315                 _nc_warning("%s differs from sgr(%d)\n\t%s=%s\n\tsgr(%d)=%s",
1316                             name, num,
1317                             name, _nc_visbuf2(1, cap),
1318                             num, _nc_visbuf2(2, test));
1319             }
1320         } else if (_nc_capcmp(test, zero)) {
1321             _nc_warning("sgr(%d) present, but not %s", num, name);
1322         }
1323     } else if (PRESENT(cap)) {
1324         _nc_warning("sgr(%d) missing, but %s present", num, name);
1325     }
1326     if (_nc_tparm_err)
1327         _nc_warning("stack error in sgr(%d) string", num);
1328     return test;
1329 }
1330
1331 #define CHECK_SGR(num,name) check_sgr(tp, zero, num, name, #name)
1332
1333 #ifdef TRACE
1334 /*
1335  * If tic is compiled with TRACE, we'll be able to see the output from the
1336  * DEBUG() macro.  But since it doesn't use traceon(), it always goes to
1337  * the standard error.  Use this function to make it simpler to follow the
1338  * resulting debug traces.
1339  */
1340 static void
1341 show_where(unsigned level)
1342 {
1343     if (_nc_tracing >= DEBUG_LEVEL(level)) {
1344         char my_name[256];
1345         _nc_get_type(my_name);
1346         fprintf(stderr, "\"%s\", line %d, '%s' ",
1347                 _nc_get_source(),
1348                 _nc_curr_line, my_name);
1349     }
1350 }
1351
1352 #else
1353 #define show_where(level)       /* nothing */
1354 #endif
1355
1356 /* other sanity-checks (things that we don't want in the normal
1357  * logic that reads a terminfo entry)
1358  */
1359 static void
1360 check_termtype(TERMTYPE *tp, bool literal)
1361 {
1362     bool conflict = FALSE;
1363     unsigned j, k;
1364     char fkeys[STRCOUNT];
1365
1366     /*
1367      * A terminal entry may contain more than one keycode assigned to
1368      * a given string (e.g., KEY_END and KEY_LL).  But curses will only
1369      * return one (the last one assigned).
1370      */
1371     if (!(_nc_syntax == SYN_TERMCAP && capdump)) {
1372         memset(fkeys, 0, sizeof(fkeys));
1373         for (j = 0; _nc_tinfo_fkeys[j].code; j++) {
1374             char *a = tp->Strings[_nc_tinfo_fkeys[j].offset];
1375             bool first = TRUE;
1376             if (!VALID_STRING(a))
1377                 continue;
1378             for (k = j + 1; _nc_tinfo_fkeys[k].code; k++) {
1379                 char *b = tp->Strings[_nc_tinfo_fkeys[k].offset];
1380                 if (!VALID_STRING(b)
1381                     || fkeys[k])
1382                     continue;
1383                 if (!_nc_capcmp(a, b)) {
1384                     fkeys[j] = 1;
1385                     fkeys[k] = 1;
1386                     if (first) {
1387                         if (!conflict) {
1388                             _nc_warning("Conflicting key definitions (using the last)");
1389                             conflict = TRUE;
1390                         }
1391                         fprintf(stderr, "... %s is the same as %s",
1392                                 keyname((int) _nc_tinfo_fkeys[j].code),
1393                                 keyname((int) _nc_tinfo_fkeys[k].code));
1394                         first = FALSE;
1395                     } else {
1396                         fprintf(stderr, ", %s",
1397                                 keyname((int) _nc_tinfo_fkeys[k].code));
1398                     }
1399                 }
1400             }
1401             if (!first)
1402                 fprintf(stderr, "\n");
1403         }
1404     }
1405
1406     for (j = 0; j < NUM_STRINGS(tp); j++) {
1407         char *a = tp->Strings[j];
1408         if (VALID_STRING(a))
1409             check_params(tp, ExtStrname(tp, j, strnames), a);
1410     }
1411
1412     check_acs(tp);
1413     check_colors(tp);
1414     check_keypad(tp);
1415
1416     /*
1417      * These may be mismatched because the terminal description relies on
1418      * restoring the cursor visibility by resetting it.
1419      */
1420     ANDMISSING(cursor_invisible, cursor_normal);
1421     ANDMISSING(cursor_visible, cursor_normal);
1422
1423     if (PRESENT(cursor_visible) && PRESENT(cursor_normal)
1424         && !_nc_capcmp(cursor_visible, cursor_normal))
1425         _nc_warning("cursor_visible is same as cursor_normal");
1426
1427     /*
1428      * From XSI & O'Reilly, we gather that sc/rc are required if csr is
1429      * given, because the cursor position after the scrolling operation is
1430      * performed is undefined.
1431      */
1432     ANDMISSING(change_scroll_region, save_cursor);
1433     ANDMISSING(change_scroll_region, restore_cursor);
1434
1435     if (PRESENT(set_attributes)) {
1436         char *zero = 0;
1437
1438         _nc_tparm_err = 0;
1439         if (PRESENT(exit_attribute_mode)) {
1440             zero = strdup(CHECK_SGR(0, exit_attribute_mode));
1441         } else {
1442             zero = strdup(TPARM_9(set_attributes, 0, 0, 0, 0, 0, 0, 0, 0, 0));
1443         }
1444         if (_nc_tparm_err)
1445             _nc_warning("stack error in sgr(0) string");
1446
1447         if (zero != 0) {
1448             CHECK_SGR(1, enter_standout_mode);
1449             CHECK_SGR(2, enter_underline_mode);
1450             CHECK_SGR(3, enter_reverse_mode);
1451             CHECK_SGR(4, enter_blink_mode);
1452             CHECK_SGR(5, enter_dim_mode);
1453             CHECK_SGR(6, enter_bold_mode);
1454             CHECK_SGR(7, enter_secure_mode);
1455             CHECK_SGR(8, enter_protected_mode);
1456             CHECK_SGR(9, enter_alt_charset_mode);
1457             free(zero);
1458         } else {
1459             _nc_warning("sgr(0) did not return a value");
1460         }
1461     } else if (PRESENT(exit_attribute_mode) &&
1462                set_attributes != CANCELLED_STRING) {
1463         if (_nc_syntax == SYN_TERMINFO)
1464             _nc_warning("missing sgr string");
1465     }
1466
1467     if (PRESENT(exit_attribute_mode)) {
1468         char *check_sgr0 = _nc_trim_sgr0(tp);
1469
1470         if (check_sgr0 == 0 || *check_sgr0 == '\0') {
1471             _nc_warning("trimmed sgr0 is empty");
1472         } else {
1473             show_where(2);
1474             if (check_sgr0 != exit_attribute_mode) {
1475                 DEBUG(2,
1476                       ("will trim sgr0\n\toriginal sgr0=%s\n\ttrimmed  sgr0=%s",
1477                        _nc_visbuf2(1, exit_attribute_mode),
1478                        _nc_visbuf2(2, check_sgr0)));
1479                 free(check_sgr0);
1480             } else {
1481                 DEBUG(2,
1482                       ("will not trim sgr0\n\toriginal sgr0=%s",
1483                        _nc_visbuf(exit_attribute_mode)));
1484             }
1485         }
1486     }
1487 #ifdef TRACE
1488     show_where(2);
1489     if (!auto_right_margin) {
1490         DEBUG(2,
1491               ("can write to lower-right directly"));
1492     } else if (PRESENT(enter_am_mode) && PRESENT(exit_am_mode)) {
1493         DEBUG(2,
1494               ("can write to lower-right by suppressing automargin"));
1495     } else if ((PRESENT(enter_insert_mode) && PRESENT(exit_insert_mode))
1496                || PRESENT(insert_character) || PRESENT(parm_ich)) {
1497         DEBUG(2,
1498               ("can write to lower-right by using inserts"));
1499     } else {
1500         DEBUG(2,
1501               ("cannot write to lower-right"));
1502     }
1503 #endif
1504
1505     /*
1506      * Some standard applications (e.g., vi) and some non-curses
1507      * applications (e.g., jove) get confused if we have both ich1 and
1508      * smir/rmir.  Let's be nice and warn about that, too, even though
1509      * ncurses handles it.
1510      */
1511     if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
1512         && PRESENT(parm_ich)) {
1513         _nc_warning("non-curses applications may be confused by ich1 with smir/rmir");
1514     }
1515
1516     /*
1517      * Finally, do the non-verbose checks
1518      */
1519     if (save_check_termtype != 0)
1520         save_check_termtype(tp, literal);
1521 }