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