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