ncurses 5.0
[ncurses.git] / ncurses / tinfo / 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_entry.h>
56
57 MODULE_ID("$Id: comp_parse.c,v 1.34 1999/02/27 22:13:02 tom Exp $")
58
59 static void sanity_check(TERMTYPE *);
60 void (*_nc_check_termtype)(TERMTYPE *) = sanity_check;
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 = _nc_copy_entry(ep);
89
90         if (newp == NULL)
91             _nc_err_abort("Out of memory");
92
93         newp->last = _nc_tail;
94         _nc_tail = newp;
95
96         newp->next = (ENTRY *)NULL;
97         if (newp->last)
98             newp->last->next = newp;
99 }
100
101 void _nc_free_entries(ENTRY *head)
102 /* free the allocated storage consumed by list entries */
103 {
104     ENTRY       *ep, *next;
105
106     for (ep = head; ep; ep = next)
107     {
108         /*
109          * This conditional lets us disconnect storage from the list.
110          * To do this, copy an entry out of the list, then null out
111          * the string-table member in the original and any use entries
112          * it references.
113          */
114         FreeIfNeeded(ep->tterm.str_table);
115
116         next = ep->next;
117
118         free(ep);
119         if (ep == _nc_head) _nc_head = 0;
120         if (ep == _nc_tail) _nc_tail = 0;
121     }
122 }
123
124 bool _nc_entry_match(char *n1, char *n2)
125 /* do any of the aliases in a pair of terminal names match? */
126 {
127     char        *pstart, *qstart, *pend, *qend;
128     char        nc1[MAX_NAME_SIZE+1], nc2[MAX_NAME_SIZE+1];
129
130     if (strchr(n1, '|') == NULL)
131     {
132         (void) strncpy(nc1, n1, sizeof(nc1) - 2);
133         nc1[sizeof(nc1) - 2] = '\0';
134         (void) strcat(nc1, "|");
135         n1 = nc1;
136     }
137
138     if (strchr(n2, '|') == NULL)
139     {
140         (void) strncpy(nc2, n2, sizeof(nc2) - 2);
141         nc2[sizeof(nc2) - 2] = '\0';
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     memset(&thisentry, 0, sizeof(thisentry));
174     for (_nc_reset_input(fp, buf); _nc_parse_entry(&thisentry, literal, silent) != ERR; )
175     {
176         if (!isalnum(thisentry.tterm.term_names[0]))
177             _nc_err_abort("terminal names must start with letter or digit");
178
179         /*
180          * This can be used for immediate compilation of entries with no
181          * use references to disk, so as to avoid chewing up a lot of
182          * core when the resolution code could fetch entries off disk.
183          */
184         if (hook != NULLHOOK && (*hook)(&thisentry))
185             immediate++;
186         else
187             enqueue(&thisentry);
188     }
189
190     if (_nc_tail)
191     {
192         /* set up the head pointer */
193         for (_nc_head = _nc_tail; _nc_head->last; _nc_head = _nc_head->last)
194             continue;
195
196         DEBUG(1, ("head = %s", _nc_head->tterm.term_names));
197         DEBUG(1, ("tail = %s", _nc_tail->tterm.term_names));
198     }
199 #ifdef TRACE
200     else if (!immediate)
201         DEBUG(1, ("no entries parsed"));
202 #endif
203
204     _nc_suppress_warnings = oldsuppress;
205 }
206
207 int _nc_resolve_uses(void)
208 /* try to resolve all use capabilities */
209 {
210     ENTRY       *qp, *rp, *lastread = NULL;
211     bool        keepgoing;
212     int         i, j, unresolved, total_unresolved, multiples;
213
214     DEBUG(2, ("RESOLUTION BEGINNING"));
215
216     /*
217      * Check for multiple occurrences of the same name.
218      */
219     multiples = 0;
220     for_entry_list(qp)
221     {
222         int matchcount = 0;
223
224         for_entry_list(rp)
225             if (qp > rp
226                 && _nc_entry_match(qp->tterm.term_names, rp->tterm.term_names))
227             {
228                 matchcount++;
229                 if (matchcount == 1)
230                 {
231                     (void) fprintf(stderr, "Name collision between %s",
232                            _nc_first_name(qp->tterm.term_names));
233                     multiples++;
234                 }
235                 if (matchcount >= 1)
236                     (void) fprintf(stderr, " %s", _nc_first_name(rp->tterm.term_names));
237             }
238         if (matchcount >= 1)
239             (void) putc('\n', stderr);
240     }
241     if (multiples > 0)
242         return(FALSE);
243
244     DEBUG(2, ("NO MULTIPLE NAME OCCURRENCES"));
245
246     /*
247      * First resolution stage: replace names in use arrays with entry
248      * pointers.  By doing this, we avoid having to do the same name
249      * match once for each time a use entry is itself unresolved.
250      */
251     total_unresolved = 0;
252     _nc_curr_col = -1;
253     for_entry_list(qp)
254     {
255         unresolved = 0;
256         for (i = 0; i < qp->nuses; i++)
257         {
258             bool        foundit;
259             char        *child = _nc_first_name(qp->tterm.term_names);
260             char        *lookfor = (char *)(qp->uses[i].parent);
261             long        lookline = qp->uses[i].line;
262
263             foundit = FALSE;
264
265             _nc_set_type(child);
266
267             /* first, try to resolve from in-core records */
268             for_entry_list(rp)
269                 if (rp != qp
270                     && _nc_name_match(rp->tterm.term_names, lookfor, "|"))
271                 {
272                     DEBUG(2, ("%s: resolving use=%s (in core)",
273                               child, lookfor));
274
275                     qp->uses[i].parent = rp;
276                     foundit = TRUE;
277                 }
278
279             /* if that didn't work, try to merge in a compiled entry */
280             if (!foundit)
281             {
282                 TERMTYPE        thisterm;
283                 char            filename[PATH_MAX];
284
285                 memset(&thisterm, 0, sizeof(thisterm));
286                 if (_nc_read_entry(lookfor, filename, &thisterm) == 1)
287                 {
288                     DEBUG(2, ("%s: resolving use=%s (compiled)",
289                               child, lookfor));
290
291                     rp = typeMalloc(ENTRY,1);
292                     if (rp == NULL)
293                         _nc_err_abort("Out of memory");
294                     rp->tterm = thisterm;
295                     rp->nuses = 0;
296                     rp->next = lastread;
297                     lastread = rp;
298
299                     qp->uses[i].parent = rp;
300                     foundit = TRUE;
301                 }
302             }
303
304             /* no good, mark this one unresolvable and complain */
305             if (!foundit)
306             {
307                 unresolved++;
308                 total_unresolved++;
309
310                 _nc_curr_line = lookline;
311                 _nc_warning("resolution of use=%s failed", lookfor);
312                 qp->uses[i].parent = (ENTRY *)NULL;
313             }
314         }
315     }
316     if (total_unresolved)
317     {
318         /* free entries read in off disk */
319         _nc_free_entries(lastread);
320         return(FALSE);
321     }
322
323     DEBUG(2, ("NAME RESOLUTION COMPLETED OK"));
324
325     /*
326      * OK, at this point all (char *) references have been successfully
327      * replaced by (ENTRY *) pointers.  Time to do the actual merges.
328      */
329     do {
330         TERMTYPE        merged;
331
332         keepgoing = FALSE;
333
334         for_entry_list(qp)
335         {
336             if (qp->nuses > 0)
337             {
338                 DEBUG(2, ("%s: attempting merge", _nc_first_name(qp->tterm.term_names)));
339                 /*
340                  * If any of the use entries we're looking for is
341                  * incomplete, punt.  We'll catch this entry on a
342                  * subsequent pass.
343                  */
344                 for (i = 0; i < qp->nuses; i++)
345                     if (((ENTRY *)qp->uses[i].parent)->nuses)
346                     {
347                         DEBUG(2, ("%s: use entry %d unresolved",
348                                   _nc_first_name(qp->tterm.term_names), i));
349                         goto incomplete;
350                     }
351
352                 /*
353                  * First, make sure there's no garbage in the merge block.
354                  * as a side effect, copy into the merged entry the name
355                  * field and string table pointer.
356                  */
357                 _nc_copy_termtype(&merged, &(qp->tterm));
358
359                 /*
360                  * Now merge in each use entry in the proper
361                  * (reverse) order.
362                  */
363                 for (; qp->nuses; qp->nuses--)
364                     _nc_merge_entry(&merged,
365                                 &((ENTRY *)qp->uses[qp->nuses-1].parent)->tterm);
366
367                 /*
368                  * Now merge in the original entry.
369                  */
370                 _nc_merge_entry(&merged, &qp->tterm);
371
372                 /*
373                  * Replace the original entry with the merged one.
374                  */
375                 FreeIfNeeded(qp->tterm.Booleans);
376                 FreeIfNeeded(qp->tterm.Numbers);
377                 FreeIfNeeded(qp->tterm.Strings);
378                 qp->tterm = merged;
379
380                 /*
381                  * We know every entry is resolvable because name resolution
382                  * didn't bomb.  So go back for another pass.
383                  */
384                 /* FALLTHRU */
385             incomplete:
386                 keepgoing = TRUE;
387             }
388         }
389     } while
390         (keepgoing);
391
392     DEBUG(2, ("MERGES COMPLETED OK"));
393
394     /*
395      * The exit condition of the loop above is such that all entries
396      * must now be resolved.  Now handle cancellations.  In a resolved
397      * entry there should be no cancellation markers.
398      */
399     for_entry_list(qp)
400     {
401         for_each_boolean(j, &(qp->tterm))
402             if (qp->tterm.Booleans[j] == CANCELLED_BOOLEAN)
403                 qp->tterm.Booleans[j] = FALSE;
404         for_each_number(j, &(qp->tterm))
405             if (qp->tterm.Numbers[j] == CANCELLED_NUMERIC)
406                 qp->tterm.Numbers[j] = ABSENT_NUMERIC;
407         for_each_string(j, &(qp->tterm))
408             if (qp->tterm.Strings[j] == CANCELLED_STRING)
409                 qp->tterm.Strings[j] = ABSENT_STRING;
410     }
411
412     /*
413      * We'd like to free entries read in off disk at this point, but can't.
414      * The merge_entry() code doesn't copy the strings in the use entries,
415      * it just aliases them.  If this ever changes, do a
416      * free_entries(lastread) here.
417      */
418
419     DEBUG(2, ("RESOLUTION FINISHED"));
420
421     if (_nc_check_termtype != 0)
422     {
423         _nc_curr_col = -1;
424         for_entry_list(qp)
425         {
426             _nc_curr_line = qp->startline;
427             _nc_set_type(_nc_first_name(qp->tterm.term_names));
428             _nc_check_termtype(&qp->tterm);
429         }
430         DEBUG(2, ("SANITY CHECK FINISHED"));
431     }
432
433     return(TRUE);
434 }
435
436 /*
437  * This bit of legerdemain turns all the terminfo variable names into
438  * references to locations in the arrays Booleans, Numbers, and Strings ---
439  * precisely what's needed.
440  */
441
442 #undef CUR
443 #define CUR tp->
444
445 static void sanity_check(TERMTYPE *tp)
446 {
447     if (!PRESENT(exit_attribute_mode))
448     {
449 #ifdef __UNUSED__       /* this casts too wide a net */
450         bool       terminal_entry = !strchr(tp->term_names, '+');
451         if (terminal_entry &&
452                 (PRESENT(set_attributes)
453                 || PRESENT(enter_standout_mode)
454                 || PRESENT(enter_underline_mode)
455                 || PRESENT(enter_blink_mode)
456                 || PRESENT(enter_bold_mode)
457                 || PRESENT(enter_dim_mode)
458                 || PRESENT(enter_secure_mode)
459                 || PRESENT(enter_protected_mode)
460                 || PRESENT(enter_reverse_mode)))
461             _nc_warning("no exit_attribute_mode");
462 #endif /* __UNUSED__ */
463         PAIRED(enter_standout_mode,     exit_standout_mode)
464         PAIRED(enter_underline_mode,    exit_underline_mode)
465     }
466
467      /* listed in structure-member order of first argument */
468      PAIRED(enter_alt_charset_mode,          exit_alt_charset_mode)
469      ANDMISSING(enter_alt_charset_mode,      acs_chars)
470      ANDMISSING(exit_alt_charset_mode,       acs_chars)
471      ANDMISSING(enter_blink_mode,            exit_attribute_mode)
472      ANDMISSING(enter_bold_mode,             exit_attribute_mode)
473      PAIRED(exit_ca_mode,                    enter_ca_mode)
474      PAIRED(enter_delete_mode,               exit_delete_mode)
475      ANDMISSING(enter_dim_mode,              exit_attribute_mode)
476      PAIRED(enter_insert_mode,               exit_insert_mode)
477      ANDMISSING(enter_secure_mode,           exit_attribute_mode)
478      ANDMISSING(enter_protected_mode,        exit_attribute_mode)
479      ANDMISSING(enter_reverse_mode,          exit_attribute_mode)
480      PAIRED(from_status_line,                to_status_line)
481      PAIRED(meta_off,                        meta_on)
482
483      PAIRED(prtr_on,                         prtr_off)
484      PAIRED(save_cursor,                     restore_cursor)
485      PAIRED(enter_xon_mode,                  exit_xon_mode)
486      PAIRED(enter_am_mode,                   exit_am_mode)
487      ANDMISSING(label_off,                   label_on)
488      PAIRED(display_clock,                   remove_clock)
489      ANDMISSING(set_color_pair,              initialize_pair)
490 }