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