ncurses 4.2
[ncurses.git] / ncurses / comp_parse.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
36 /*
37  *      comp_parse.c -- parser driver loop and use handling.
38  *
39  *      _nc_read_entry_source(FILE *, literal, bool, bool (*hook)())
40  *      _nc_resolve_uses(void)
41  *      _nc_free_entries(void)
42  *
43  *      Use this code by calling _nc_read_entry_source() on as many source
44  *      files as you like (either terminfo or termcap syntax).  If you
45  *      want use-resolution, call _nc_resolve_uses().  To free the list
46  *      storage, do _nc_free_entries().
47  *
48  */
49
50 #include <curses.priv.h>
51
52 #include <ctype.h>
53
54 #include <tic.h>
55 #include <term.h>
56 #include <term_entry.h>
57
58 MODULE_ID("$Id: comp_parse.c,v 1.21 1998/02/11 12:14:00 tom Exp $")
59
60 static void sanity_check(TERMTYPE *);
61
62 /****************************************************************************
63  *
64  * Entry queue handling
65  *
66  ****************************************************************************/
67 /*
68  *  The entry list is a doubly linked list with NULLs terminating the lists:
69  *
70  *        ---------   ---------   ---------
71  *        |       |   |       |   |       |   offset
72  *        |-------|   |-------|   |-------|
73  *        |   ----+-->|   ----+-->|  NULL |   next
74  *        |-------|   |-------|   |-------|
75  *        |  NULL |<--+----   |<--+----   |   last
76  *        ---------   ---------   ---------
77  *            ^                       ^
78  *            |                       |
79  *            |                       |
80  *         _nc_head                _nc_tail
81  */
82
83 ENTRY *_nc_head, *_nc_tail;
84
85 static void enqueue(ENTRY *ep)
86 /* add an entry to the in-core list */
87 {
88         ENTRY   *newp = (ENTRY *)malloc(sizeof(ENTRY));
89
90         if (newp == NULL)
91             _nc_err_abort("Out of memory");
92
93         (void) memcpy(newp, ep, sizeof(ENTRY));
94
95         newp->last = _nc_tail;
96         _nc_tail = newp;
97
98         newp->next = (ENTRY *)NULL;
99         if (newp->last)
100             newp->last->next = newp;
101 }
102
103 void _nc_free_entries(ENTRY *head)
104 /* free the allocated storage consumed by list entries */
105 {
106     ENTRY       *ep, *next;
107
108     for (ep = head; ep; ep = next)
109     {
110         /*
111          * This conditional lets us disconnect storage from the list.
112          * To do this, copy an entry out of the list, then null out
113          * the string-table member in the original and any use entries
114          * it references.
115          */
116         FreeIfNeeded(ep->tterm.str_table);
117
118         next = ep->next;
119
120         free(ep);
121         if (ep == _nc_head) _nc_head = 0;
122         if (ep == _nc_tail) _nc_tail = 0;
123     }
124 }
125
126 bool _nc_entry_match(char *n1, char *n2)
127 /* do any of the aliases in a pair of terminal names match? */
128 {
129     char        *pstart, *qstart, *pend, *qend;
130     char        nc1[MAX_NAME_SIZE+1], nc2[MAX_NAME_SIZE+1];
131
132     if (strchr(n1, '|') == NULL)
133     {
134         (void) strcpy(nc1, n1);
135         (void) strcat(nc1, "|");
136         n1 = nc1;
137     }
138
139     if (strchr(n2, '|') == NULL)
140     {
141         (void) strcpy(nc2, n2);
142         (void) strcat(nc2, "|");
143         n2 = nc2;
144     }
145
146     for (pstart = n1; (pend = strchr(pstart, '|')); pstart = pend + 1)
147         for (qstart = n2; (qend = strchr(qstart, '|')); qstart = qend + 1)
148             if ((pend-pstart == qend-qstart)
149              && memcmp(pstart, qstart, (size_t)(pend-pstart)) == 0)
150                 return(TRUE);
151
152         return(FALSE);
153 }
154
155 /****************************************************************************
156  *
157  * Entry compiler and resolution logic
158  *
159  ****************************************************************************/
160
161 void _nc_read_entry_source(FILE *fp, char *buf,
162                            int literal, bool silent,
163                            bool (*hook)(ENTRY *))
164 /* slurp all entries in the given file into core */
165 {
166     ENTRY       thisentry;
167     bool        oldsuppress = _nc_suppress_warnings;
168     int         immediate = 0;
169
170     if (silent)
171         _nc_suppress_warnings = TRUE;   /* shut the lexer up, too */
172
173     for (_nc_reset_input(fp, buf); _nc_parse_entry(&thisentry, literal, silent) != ERR; )
174     {
175         if (!isalnum(thisentry.tterm.term_names[0]))
176             _nc_err_abort("terminal names must start with letter or digit");
177
178         /*
179          * This can be used for immediate compilation of entries with no
180          * use references to disk, so as to avoid chewing up a lot of
181          * core when the resolution code could fetch entries off disk.
182          */
183         if (hook != NULLHOOK && (*hook)(&thisentry))
184             immediate++;
185         else
186             enqueue(&thisentry);
187     }
188
189     if (_nc_tail)
190     {
191         /* set up the head pointer */
192         for (_nc_head = _nc_tail; _nc_head->last; _nc_head = _nc_head->last)
193             continue;
194
195         DEBUG(1, ("head = %s", _nc_head->tterm.term_names));
196         DEBUG(1, ("tail = %s", _nc_tail->tterm.term_names));
197     }
198     else if (!immediate)
199         DEBUG(1, ("no entries parsed"));
200
201     _nc_suppress_warnings = oldsuppress;
202 }
203
204 int _nc_resolve_uses(void)
205 /* try to resolve all use capabilities */
206 {
207     ENTRY       *qp, *rp, *lastread = NULL;
208     bool        keepgoing;
209     int         i, j, unresolved, total_unresolved, multiples;
210
211     DEBUG(2, ("RESOLUTION BEGINNING"));
212
213     /*
214      * Check for multiple occurrences of the same name.
215      */
216     multiples = 0;
217     for_entry_list(qp)
218     {
219         int matchcount = 0;
220
221         for_entry_list(rp)
222             if (qp > rp
223                 && _nc_entry_match(qp->tterm.term_names, rp->tterm.term_names))
224             {
225                 matchcount++;
226                 if (matchcount == 1)
227                 {
228                     (void) fprintf(stderr, "Name collision between %s",
229                            _nc_first_name(qp->tterm.term_names));
230                     multiples++;
231                 }
232                 if (matchcount >= 1)
233                     (void) fprintf(stderr, " %s", _nc_first_name(rp->tterm.term_names));
234             }
235         if (matchcount >= 1)
236             (void) putc('\n', stderr);
237     }
238     if (multiples > 0)
239         return(FALSE);
240
241     DEBUG(2, ("NO MULTIPLE NAME OCCURRENCES"));
242
243     /*
244      * First resolution stage: replace names in use arrays with entry
245      * pointers.  By doing this, we avoid having to do the same name
246      * match once for each time a use entry is itself unresolved.
247      */
248     total_unresolved = 0;
249     _nc_curr_col = -1;
250     for_entry_list(qp)
251     {
252         unresolved = 0;
253         for (i = 0; i < qp->nuses; i++)
254         {
255             bool        foundit;
256             char        *child = _nc_first_name(qp->tterm.term_names);
257             char        *lookfor = (char *)(qp->uses[i].parent);
258             long        lookline = qp->uses[i].line;
259
260             foundit = FALSE;
261
262             _nc_set_type(child);
263
264             /* first, try to resolve from in-core records */
265             for_entry_list(rp)
266                 if (rp != qp
267                     && _nc_name_match(rp->tterm.term_names, lookfor, "|"))
268                 {
269                     DEBUG(2, ("%s: resolving use=%s (in core)",
270                               child, lookfor));
271
272                     qp->uses[i].parent = rp;
273                     foundit = TRUE;
274                 }
275
276             /* if that didn't work, try to merge in a compiled entry */
277             if (!foundit)
278             {
279                 TERMTYPE        thisterm;
280                 char            filename[PATH_MAX];
281
282                 if (_nc_read_entry(lookfor, filename, &thisterm) == 1)
283                 {
284                     DEBUG(2, ("%s: resolving use=%s (compiled)",
285                               child, lookfor));
286
287                     rp = (ENTRY *)malloc(sizeof(ENTRY));
288                     memcpy(&rp->tterm, &thisterm, sizeof(TERMTYPE));
289                     rp->nuses = 0;
290                     rp->next = lastread;
291                     lastread = rp;
292
293                     qp->uses[i].parent = rp;
294                     foundit = TRUE;
295                 }
296             }
297
298             /* no good, mark this one unresolvable and complain */
299             if (!foundit)
300             {
301                 unresolved++;
302                 total_unresolved++;
303
304                 _nc_curr_line = lookline;
305                 _nc_warning("resolution of use=%s failed", lookfor);
306                 qp->uses[i].parent = (ENTRY *)NULL;
307             }
308         }
309     }
310     if (total_unresolved)
311     {
312         /* free entries read in off disk */
313         _nc_free_entries(lastread);
314         return(FALSE);
315     }
316
317     DEBUG(2, ("NAME RESOLUTION COMPLETED OK"));
318
319     /*
320      * OK, at this point all (char *) references have been successfully
321      * replaced by (ENTRY *) pointers.  Time to do the actual merges.
322      */
323     do {
324         TERMTYPE        merged;
325
326         keepgoing = FALSE;
327
328         for_entry_list(qp)
329             if (qp->nuses > 0)
330             {
331                 DEBUG(2, ("%s: attempting merge", _nc_first_name(qp->tterm.term_names)));
332                 /*
333                  * If any of the use entries we're looking for is
334                  * incomplete, punt.  We'll catch this entry on a
335                  * subsequent pass.
336                  */
337                 for (i = 0; i < qp->nuses; i++)
338                     if (((ENTRY *)qp->uses[i].parent)->nuses)
339                     {
340                         DEBUG(2, ("%s: use entry %d unresolved",
341                                   _nc_first_name(qp->tterm.term_names), i));
342                         goto incomplete;
343                     }
344
345                 /*
346                  * First, make sure there's no garbage in the merge block.
347                  * as a side effect, copy into the merged entry the name
348                  * field and string table pointer.
349                  */
350                 memcpy(&merged, &qp->tterm, sizeof(TERMTYPE));
351
352                 /*
353                  * Now merge in each use entry in the proper
354                  * (reverse) order.
355                  */
356                 for (; qp->nuses; qp->nuses--)
357                     _nc_merge_entry(&merged,
358                                 &((ENTRY *)qp->uses[qp->nuses-1].parent)->tterm);
359
360                 /*
361                  * Now merge in the original entry.
362                  */
363                 _nc_merge_entry(&merged, &qp->tterm);
364
365                 /*
366                  * Replace the original entry with the merged one.
367                  */
368                 memcpy(&qp->tterm, &merged, sizeof(TERMTYPE));
369
370                 /*
371                  * We know every entry is resolvable because name resolution
372                  * didn't bomb.  So go back for another pass.
373                  */
374                 /* FALLTHRU */
375             incomplete:
376                 keepgoing = TRUE;
377             }
378     } while
379         (keepgoing);
380
381     DEBUG(2, ("MERGES COMPLETED OK"));
382
383     /*
384      * The exit condition of the loop above is such that all entries
385      * must now be resolved.  Now handle cancellations.  In a resolved
386      * entry there should be no cancellation markers.
387      */
388     for_entry_list(qp)
389     {
390         for (j = 0; j < BOOLCOUNT; j++)
391             if (qp->tterm.Booleans[j] == CANCELLED_BOOLEAN)
392                 qp->tterm.Booleans[j] = FALSE;
393         for (j = 0; j < NUMCOUNT; j++)
394             if (qp->tterm.Numbers[j] == CANCELLED_NUMERIC)
395                 qp->tterm.Numbers[j] = ABSENT_NUMERIC;
396         for (j = 0; j < STRCOUNT; j++)
397             if (qp->tterm.Strings[j] == CANCELLED_STRING)
398                 qp->tterm.Strings[j] = ABSENT_STRING;
399     }
400
401     /*
402      * We'd like to free entries read in off disk at this point, but can't.
403      * The merge_entry() code doesn't copy the strings in the use entries,
404      * it just aliases them.  If this ever changes, do a
405      * free_entries(lastread) here.
406      */
407
408     DEBUG(2, ("RESOLUTION FINISHED"));
409
410     _nc_curr_col = -1;
411     for_entry_list(qp)
412     {
413         _nc_curr_line = qp->startline;
414         _nc_set_type(_nc_first_name(qp->tterm.term_names));
415         sanity_check(&qp->tterm);
416     }
417
418     DEBUG(2, ("SANITY CHECK FINISHED"));
419
420     return(TRUE);
421 }
422
423 /*
424  * This bit of legerdemain turns all the terminfo variable names into
425  * references to locations in the arrays Booleans, Numbers, and Strings ---
426  * precisely what's needed.
427  */
428
429 #undef CUR
430 #define CUR tp->
431
432 /*
433  * Note that WANTED and PRESENT are not simple inverses!  If a capability
434  * has been explicitly cancelled, it's not considered WANTED.
435  */
436 #define WANTED(s)       ((s) == ABSENT_STRING)
437 #define PRESENT(s)      (((s) != ABSENT_STRING) && ((s) != CANCELLED_STRING))
438
439 #define ANDMISSING(p,q) \
440                 {if (PRESENT(p) && !PRESENT(q)) _nc_warning(#p " but no " #q);}
441
442 #define PAIRED(p,q) \
443                 { \
444                 if (PRESENT(q) && !PRESENT(p)) \
445                         _nc_warning(#q " but no " #p); \
446                 if (PRESENT(p) && !PRESENT(q)) \
447                         _nc_warning(#p " but no " #q); \
448                 }
449
450 static void sanity_check(TERMTYPE *tp)
451 {
452 #ifdef __UNUSED__       /* this casts too wide a net */
453     bool       terminal_entry = !strchr(tp->term_names, '+');
454 #endif
455
456     if (!PRESENT(exit_attribute_mode))
457     {
458 #ifdef __UNUSED__       /* this casts too wide a net */
459         if (terminal_entry &&
460                 (PRESENT(set_attributes)
461                 || PRESENT(enter_standout_mode)
462                 || PRESENT(enter_underline_mode)
463                 || PRESENT(enter_blink_mode)
464                 || PRESENT(enter_bold_mode)
465                 || PRESENT(enter_dim_mode)
466                 || PRESENT(enter_secure_mode)
467                 || PRESENT(enter_protected_mode)
468                 || PRESENT(enter_reverse_mode)))
469             _nc_warning("no exit_attribute_mode");
470 #endif /* __UNUSED__ */
471         PAIRED(enter_standout_mode,     exit_standout_mode)
472         PAIRED(enter_underline_mode,    exit_underline_mode)
473     }
474
475      /* listed in structure-member order of first argument */
476 #ifdef __UNUSED__
477      ANDMISSING(cursor_invisible,            cursor_normal)
478      ANDMISSING(cursor_visible,              cursor_normal)
479 #endif /* __UNUSED__ */
480      PAIRED(enter_alt_charset_mode,          exit_alt_charset_mode)
481      ANDMISSING(enter_alt_charset_mode,      acs_chars)
482      ANDMISSING(exit_alt_charset_mode,       acs_chars)
483      ANDMISSING(enter_blink_mode,            exit_attribute_mode)
484      ANDMISSING(enter_bold_mode,             exit_attribute_mode)
485      PAIRED(exit_ca_mode,                    enter_ca_mode)
486      PAIRED(enter_delete_mode,               exit_delete_mode)
487      ANDMISSING(enter_dim_mode,              exit_attribute_mode)
488      PAIRED(enter_insert_mode,               exit_insert_mode)
489      ANDMISSING(enter_secure_mode,           exit_attribute_mode)
490      ANDMISSING(enter_protected_mode,        exit_attribute_mode)
491      ANDMISSING(enter_reverse_mode,          exit_attribute_mode)
492      PAIRED(from_status_line,                to_status_line)
493      PAIRED(meta_off,                        meta_on)
494
495      PAIRED(prtr_on,                         prtr_off)
496      PAIRED(save_cursor,                     restore_cursor)
497      PAIRED(enter_xon_mode,                  exit_xon_mode)
498      PAIRED(enter_am_mode,                   exit_am_mode)
499      ANDMISSING(label_off,                   label_on)
500      PAIRED(display_clock,                   remove_clock)
501      ANDMISSING(set_color_pair,              initialize_pair)
502
503      /* Some checks that we should make, but don't want to confuse people
504       * with.  Put those under the tic -v option so we can still get them.
505       */
506      if (_nc_tracing) {
507
508         /*
509          * From XSI & O'Reilly, we gather that sc/rc are required if csr is
510          * given, because the cursor position after the scrolling operation is
511          * performed is undefined.
512          */
513          ANDMISSING(change_scroll_region,        save_cursor)
514          ANDMISSING(change_scroll_region,        restore_cursor)
515
516          /*
517           * Some non-curses applications (e.g., jove) get confused if we have
518           * both ich/ich1 and smir/rmir.  Let's be nice and warn about that,
519           * too, even though ncurses handles it.
520           */
521          if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
522           && (PRESENT(insert_character)  || PRESENT(parm_ich))) {
523             _nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir");
524          }
525      }
526 #undef PAIRED
527 #undef ANDMISSING
528 }