ncurses 5.1
[ncurses.git] / ncurses / tinfo / comp_parse.c
1 /****************************************************************************
2  * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  ****************************************************************************/
33
34 /*
35  *      comp_parse.c -- parser driver loop and use handling.
36  *
37  *      _nc_read_entry_source(FILE *, literal, bool, bool (*hook)())
38  *      _nc_resolve_uses(void)
39  *      _nc_free_entries(void)
40  *
41  *      Use this code by calling _nc_read_entry_source() on as many source
42  *      files as you like (either terminfo or termcap syntax).  If you
43  *      want use-resolution, call _nc_resolve_uses().  To free the list
44  *      storage, do _nc_free_entries().
45  *
46  */
47
48 #include <curses.priv.h>
49
50 #include <ctype.h>
51
52 #include <tic.h>
53 #include <term_entry.h>
54
55 MODULE_ID("$Id: comp_parse.c,v 1.40 2000/04/15 16:57:08 tom Exp $")
56
57 static void sanity_check(TERMTYPE *);
58 void (*_nc_check_termtype) (TERMTYPE *) = sanity_check;
59
60 /****************************************************************************
61  *
62  * Entry queue handling
63  *
64  ****************************************************************************/
65 /*
66  *  The entry list is a doubly linked list with NULLs terminating the lists:
67  *
68  *        ---------   ---------   ---------
69  *        |       |   |       |   |       |   offset
70  *        |-------|   |-------|   |-------|
71  *        |   ----+-->|   ----+-->|  NULL |   next
72  *        |-------|   |-------|   |-------|
73  *        |  NULL |<--+----   |<--+----   |   last
74  *        ---------   ---------   ---------
75  *            ^                       ^
76  *            |                       |
77  *            |                       |
78  *         _nc_head                _nc_tail
79  */
80
81 ENTRY *_nc_head = 0, *_nc_tail = 0;
82
83 static void
84 enqueue(ENTRY * ep)
85 /* add an entry to the in-core list */
86 {
87     ENTRY *newp = _nc_copy_entry(ep);
88
89     if (newp == 0)
90         _nc_err_abort("Out of memory");
91
92     newp->last = _nc_tail;
93     _nc_tail = newp;
94
95     newp->next = 0;
96     if (newp->last)
97         newp->last->next = newp;
98 }
99
100 void
101 _nc_free_entries(ENTRY * headp)
102 /* free the allocated storage consumed by list entries */
103 {
104     ENTRY *ep, *next;
105
106     for (ep = headp; ep; ep = next) {
107         /*
108          * This conditional lets us disconnect storage from the list.
109          * To do this, copy an entry out of the list, then null out
110          * the string-table member in the original and any use entries
111          * it references.
112          */
113         FreeIfNeeded(ep->tterm.str_table);
114
115         next = ep->next;
116
117         free(ep);
118         if (ep == _nc_head)
119             _nc_head = 0;
120         if (ep == _nc_tail)
121             _nc_tail = 0;
122     }
123 }
124
125 bool
126 _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, '|') == 0) {
133         (void) strncpy(nc1, n1, sizeof(nc1) - 2);
134         nc1[sizeof(nc1) - 2] = '\0';
135         (void) strcat(nc1, "|");
136         n1 = nc1;
137     }
138
139     if (strchr(n2, '|') == 0) {
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
162 _nc_read_entry_source(FILE * fp, char *buf,
163     int literal, bool silent,
164     bool(*hook) (ENTRY *))
165 /* slurp all entries in the given file into core */
166 {
167     ENTRY thisentry;
168     bool oldsuppress = _nc_suppress_warnings;
169     int immediate = 0;
170
171     if (silent)
172         _nc_suppress_warnings = TRUE;   /* shut the lexer up, too */
173
174     _nc_reset_input(fp, buf);
175     for (;;) {
176         memset(&thisentry, 0, sizeof(thisentry));
177         if (_nc_parse_entry(&thisentry, literal, silent) == ERR)
178             break;
179         if (!isalnum(thisentry.tterm.term_names[0]))
180             _nc_err_abort("terminal names must start with letter or digit");
181
182         /*
183          * This can be used for immediate compilation of entries with no
184          * use references to disk, so as to avoid chewing up a lot of
185          * core when the resolution code could fetch entries off disk.
186          */
187         if (hook != NULLHOOK && (*hook) (&thisentry))
188             immediate++;
189         else
190             enqueue(&thisentry);
191     }
192
193     if (_nc_tail) {
194         /* set up the head pointer */
195         for (_nc_head = _nc_tail; _nc_head->last; _nc_head = _nc_head->last)
196             continue;
197
198         DEBUG(1, ("head = %s", _nc_head->tterm.term_names));
199         DEBUG(1, ("tail = %s", _nc_tail->tterm.term_names));
200     }
201 #ifdef TRACE
202     else if (!immediate)
203         DEBUG(1, ("no entries parsed"));
204 #endif
205
206     _nc_suppress_warnings = oldsuppress;
207 }
208
209 int
210 _nc_resolve_uses(bool fullresolve)
211 /* try to resolve all use capabilities */
212 {
213     ENTRY *qp, *rp, *lastread = 0;
214     bool keepgoing;
215     int i, j, unresolved, total_unresolved, multiples;
216
217     DEBUG(2, ("RESOLUTION BEGINNING"));
218
219     /*
220      * Check for multiple occurrences of the same name.
221      */
222     multiples = 0;
223     for_entry_list(qp) {
224         int matchcount = 0;
225
226         for_entry_list(rp)
227             if (qp > rp
228             && _nc_entry_match(qp->tterm.term_names, rp->tterm.term_names)) {
229             matchcount++;
230             if (matchcount == 1) {
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: compute link pointers corresponding to names.
248      */
249     total_unresolved = 0;
250     _nc_curr_col = -1;
251     for_entry_list(qp) {
252         unresolved = 0;
253         for (i = 0; i < qp->nuses; i++) {
254             bool foundit;
255             char *child = _nc_first_name(qp->tterm.term_names);
256             char *lookfor = qp->uses[i].name;
257             long lookline = qp->uses[i].line;
258
259             foundit = FALSE;
260
261             _nc_set_type(child);
262
263             /* first, try to resolve from in-core records */
264             for_entry_list(rp)
265                 if (rp != qp
266                 && _nc_name_match(rp->tterm.term_names, lookfor, "|")) {
267                 DEBUG(2, ("%s: resolving use=%s (in core)",
268                         child, lookfor));
269
270                 qp->uses[i].link = rp;
271                 foundit = TRUE;
272             }
273
274             /* if that didn't work, try to merge in a compiled entry */
275             if (!foundit) {
276                 TERMTYPE thisterm;
277                 char filename[PATH_MAX];
278
279                 memset(&thisterm, 0, sizeof(thisterm));
280                 if (_nc_read_entry(lookfor, filename, &thisterm) == 1) {
281                     DEBUG(2, ("%s: resolving use=%s (compiled)",
282                             child, lookfor));
283
284                     rp = typeMalloc(ENTRY, 1);
285                     if (rp == 0)
286                         _nc_err_abort("Out of memory");
287                     rp->tterm = thisterm;
288                     rp->nuses = 0;
289                     rp->next = lastread;
290                     lastread = rp;
291
292                     qp->uses[i].link = rp;
293                     foundit = TRUE;
294                 }
295             }
296
297             /* no good, mark this one unresolvable and complain */
298             if (!foundit) {
299                 unresolved++;
300                 total_unresolved++;
301
302                 _nc_curr_line = lookline;
303                 _nc_warning("resolution of use=%s failed", lookfor);
304                 qp->uses[i].link = 0;
305             }
306         }
307     }
308     if (total_unresolved) {
309         /* free entries read in off disk */
310         _nc_free_entries(lastread);
311         return (FALSE);
312     }
313
314     DEBUG(2, ("NAME RESOLUTION COMPLETED OK"));
315
316     /*
317      * OK, at this point all (char *) references in `name' mwmbers
318      * have been successfully converred to (ENTRY *) pointers in
319      * `link' members.  Time to do the actual merges.
320      */
321     if (fullresolve) {
322         do {
323             TERMTYPE merged;
324
325             keepgoing = FALSE;
326
327             for_entry_list(qp) {
328                 if (qp->nuses > 0) {
329                     DEBUG(2, ("%s: attempting merge",
330                             _nc_first_name(qp->tterm.term_names)));
331                     /*
332                      * If any of the use entries we're looking for is
333                      * incomplete, punt.  We'll catch this entry on a
334                      * subsequent pass.
335                      */
336                     for (i = 0; i < qp->nuses; i++)
337                         if (qp->uses[i].link->nuses) {
338                             DEBUG(2, ("%s: use entry %d unresolved",
339                                     _nc_first_name(qp->tterm.term_names), i));
340                             goto incomplete;
341                         }
342
343                     /*
344                        * First, make sure there's no garbage in the
345                        * merge block.  as a side effect, copy into
346                        * the merged entry the name field and string
347                        * table pointer.
348                      */
349                     _nc_copy_termtype(&merged, &(qp->tterm));
350
351                     /*
352                      * Now merge in each use entry in the proper
353                      * (reverse) order.
354                      */
355                     for (; qp->nuses; qp->nuses--)
356                         _nc_merge_entry(&merged,
357                             &qp->uses[qp->nuses - 1].link->tterm);
358
359                     /*
360                      * Now merge in the original entry.
361                      */
362                     _nc_merge_entry(&merged, &qp->tterm);
363
364                     /*
365                      * Replace the original entry with the merged one.
366                      */
367                     FreeIfNeeded(qp->tterm.Booleans);
368                     FreeIfNeeded(qp->tterm.Numbers);
369                     FreeIfNeeded(qp->tterm.Strings);
370                     qp->tterm = merged;
371
372                     /*
373                      * We know every entry is resolvable because name resolution
374                      * didn't bomb.  So go back for another pass.
375                      */
376                     /* FALLTHRU */
377                   incomplete:
378                     keepgoing = TRUE;
379                 }
380             }
381         } while
382             (keepgoing);
383
384         DEBUG(2, ("MERGES COMPLETED OK"));
385
386         /*
387          * The exit condition of the loop above is such that all entries
388          * must now be resolved.  Now handle cancellations.  In a resolved
389          * entry there should be no cancellation markers.
390          */
391         for_entry_list(qp) {
392             for_each_boolean(j, &(qp->tterm))
393                 if (qp->tterm.Booleans[j] == CANCELLED_BOOLEAN)
394                 qp->tterm.Booleans[j] = ABSENT_BOOLEAN;
395             for_each_number(j, &(qp->tterm))
396                 if (qp->tterm.Numbers[j] == CANCELLED_NUMERIC)
397                 qp->tterm.Numbers[j] = ABSENT_NUMERIC;
398             for_each_string(j, &(qp->tterm))
399                 if (qp->tterm.Strings[j] == CANCELLED_STRING)
400                 qp->tterm.Strings[j] = ABSENT_STRING;
401         }
402     }
403
404     /*
405      * We'd like to free entries read in off disk at this point, but can't.
406      * The merge_entry() code doesn't copy the strings in the use entries,
407      * it just aliases them.  If this ever changes, do a
408      * free_entries(lastread) here.
409      */
410
411     DEBUG(2, ("RESOLUTION FINISHED"));
412
413     if (fullresolve)
414         if (_nc_check_termtype != 0) {
415             _nc_curr_col = -1;
416             for_entry_list(qp) {
417                 _nc_curr_line = qp->startline;
418                 _nc_set_type(_nc_first_name(qp->tterm.term_names));
419                 _nc_check_termtype(&qp->tterm);
420             }
421             DEBUG(2, ("SANITY CHECK FINISHED"));
422         }
423
424     return (TRUE);
425 }
426
427 /*
428  * This bit of legerdemain turns all the terminfo variable names into
429  * references to locations in the arrays Booleans, Numbers, and Strings ---
430  * precisely what's needed.
431  */
432
433 #undef CUR
434 #define CUR tp->
435
436 static void
437 sanity_check(TERMTYPE * tp)
438 {
439     if (!PRESENT(exit_attribute_mode)) {
440 #ifdef __UNUSED__               /* this casts too wide a net */
441         bool terminal_entry = !strchr(tp->term_names, '+');
442         if (terminal_entry &&
443             (PRESENT(set_attributes)
444                 || PRESENT(enter_standout_mode)
445                 || PRESENT(enter_underline_mode)
446                 || PRESENT(enter_blink_mode)
447                 || PRESENT(enter_bold_mode)
448                 || PRESENT(enter_dim_mode)
449                 || PRESENT(enter_secure_mode)
450                 || PRESENT(enter_protected_mode)
451                 || PRESENT(enter_reverse_mode)))
452             _nc_warning("no exit_attribute_mode");
453 #endif /* __UNUSED__ */
454         PAIRED(enter_standout_mode, exit_standout_mode)
455             PAIRED(enter_underline_mode, exit_underline_mode)
456     }
457
458     /* listed in structure-member order of first argument */
459     PAIRED(enter_alt_charset_mode, exit_alt_charset_mode);
460     ANDMISSING(enter_alt_charset_mode, acs_chars);
461     ANDMISSING(exit_alt_charset_mode, acs_chars);
462     ANDMISSING(enter_blink_mode, exit_attribute_mode);
463     ANDMISSING(enter_bold_mode, exit_attribute_mode);
464     PAIRED(exit_ca_mode, enter_ca_mode);
465     PAIRED(enter_delete_mode, exit_delete_mode);
466     ANDMISSING(enter_dim_mode, exit_attribute_mode);
467     PAIRED(enter_insert_mode, exit_insert_mode);
468     ANDMISSING(enter_secure_mode, exit_attribute_mode);
469     ANDMISSING(enter_protected_mode, exit_attribute_mode);
470     ANDMISSING(enter_reverse_mode, exit_attribute_mode);
471     PAIRED(from_status_line, to_status_line);
472     PAIRED(meta_off, meta_on);
473
474     PAIRED(prtr_on, prtr_off);
475     PAIRED(save_cursor, restore_cursor);
476     PAIRED(enter_xon_mode, exit_xon_mode);
477     PAIRED(enter_am_mode, exit_am_mode);
478     ANDMISSING(label_off, label_on);
479     PAIRED(display_clock, remove_clock);
480     ANDMISSING(set_color_pair, initialize_pair);
481 }