xref: /openbsd-src/lib/libcurses/tinfo/comp_parse.c (revision 92dd1ec0a89df25171bc5d61a3d95ea1a68cef0b)
1 /*	$OpenBSD: comp_parse.c,v 1.1 1999/01/18 19:10:14 millert Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998 Free Software Foundation, Inc.                        *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
33  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
34  ****************************************************************************/
35 
36 
37 
38 /*
39  *	comp_parse.c -- parser driver loop and use handling.
40  *
41  *	_nc_read_entry_source(FILE *, literal, bool, bool (*hook)())
42  *	_nc_resolve_uses(void)
43  *	_nc_free_entries(void)
44  *
45  *	Use this code by calling _nc_read_entry_source() on as many source
46  *	files as you like (either terminfo or termcap syntax).  If you
47  *	want use-resolution, call _nc_resolve_uses().  To free the list
48  *	storage, do _nc_free_entries().
49  *
50  */
51 
52 #include <curses.priv.h>
53 
54 #include <ctype.h>
55 
56 #include <tic.h>
57 #include <term.h>
58 #include <term_entry.h>
59 
60 MODULE_ID("$From: comp_parse.c,v 1.23 1998/05/30 23:38:15 Todd.Miller Exp $")
61 
62 static void sanity_check(TERMTYPE *);
63 
64 /****************************************************************************
65  *
66  * Entry queue handling
67  *
68  ****************************************************************************/
69 /*
70  *  The entry list is a doubly linked list with NULLs terminating the lists:
71  *
72  *	  ---------   ---------   ---------
73  *	  |       |   |       |   |       |   offset
74  *        |-------|   |-------|   |-------|
75  *	  |   ----+-->|   ----+-->|  NULL |   next
76  *	  |-------|   |-------|   |-------|
77  *	  |  NULL |<--+----   |<--+----   |   last
78  *	  ---------   ---------   ---------
79  *	      ^                       ^
80  *	      |                       |
81  *	      |                       |
82  *	   _nc_head                _nc_tail
83  */
84 
85 ENTRY *_nc_head, *_nc_tail;
86 
87 static void enqueue(ENTRY *ep)
88 /* add an entry to the in-core list */
89 {
90 	ENTRY	*newp = (ENTRY *)malloc(sizeof(ENTRY));
91 
92 	if (newp == NULL)
93 	    _nc_err_abort("Out of memory");
94 
95 	(void) memcpy(newp, ep, sizeof(ENTRY));
96 
97 	newp->last = _nc_tail;
98 	_nc_tail = newp;
99 
100 	newp->next = (ENTRY *)NULL;
101 	if (newp->last)
102 	    newp->last->next = newp;
103 }
104 
105 void _nc_free_entries(ENTRY *head)
106 /* free the allocated storage consumed by list entries */
107 {
108     ENTRY	*ep, *next;
109 
110     for (ep = head; ep; ep = next)
111     {
112 	/*
113 	 * This conditional lets us disconnect storage from the list.
114 	 * To do this, copy an entry out of the list, then null out
115 	 * the string-table member in the original and any use entries
116 	 * it references.
117 	 */
118 	FreeIfNeeded(ep->tterm.str_table);
119 
120 	next = ep->next;
121 
122 	free(ep);
123 	if (ep == _nc_head) _nc_head = 0;
124 	if (ep == _nc_tail) _nc_tail = 0;
125     }
126 }
127 
128 bool _nc_entry_match(char *n1, char *n2)
129 /* do any of the aliases in a pair of terminal names match? */
130 {
131     char	*pstart, *qstart, *pend, *qend;
132     char	nc1[MAX_NAME_SIZE+1], nc2[MAX_NAME_SIZE+1];
133     size_t	n;
134 
135     if (strchr(n1, '|') == NULL)
136     {
137 	if ((n = strlcpy(nc1, n1, sizeof(nc1))) > sizeof(nc1) - 2)
138 	    n = sizeof(nc1) - 2;
139 	nc1[n++] = '|';
140 	nc1[n] = '\0';
141 	n1 = nc1;
142     }
143 
144     if (strchr(n2, '|') == NULL)
145     {
146 	if ((n = strlcpy(nc2, n2, sizeof(nc2))) > sizeof(nc2) - 2)
147 	    n = sizeof(nc2) - 2;
148 	nc2[n++] = '|';
149 	nc2[n] = '\0';
150 	n2 = nc2;
151     }
152 
153     for (pstart = n1; (pend = strchr(pstart, '|')); pstart = pend + 1)
154 	for (qstart = n2; (qend = strchr(qstart, '|')); qstart = qend + 1)
155 	    if ((pend-pstart == qend-qstart)
156 	     && memcmp(pstart, qstart, (size_t)(pend-pstart)) == 0)
157 		return(TRUE);
158 
159 	return(FALSE);
160 }
161 
162 /****************************************************************************
163  *
164  * Entry compiler and resolution logic
165  *
166  ****************************************************************************/
167 
168 void _nc_read_entry_source(FILE *fp, char *buf,
169 			   int literal, bool silent,
170 			   bool (*hook)(ENTRY *))
171 /* slurp all entries in the given file into core */
172 {
173     ENTRY	thisentry;
174     bool	oldsuppress = _nc_suppress_warnings;
175     int		immediate = 0;
176 
177     if (silent)
178 	_nc_suppress_warnings = TRUE;	/* shut the lexer up, too */
179 
180     for (_nc_reset_input(fp, buf); _nc_parse_entry(&thisentry, literal, silent) != ERR; )
181     {
182 	if (!isalnum(thisentry.tterm.term_names[0]))
183 	    _nc_err_abort("terminal names must start with letter or digit");
184 
185 	/*
186 	 * This can be used for immediate compilation of entries with no
187 	 * use references to disk, so as to avoid chewing up a lot of
188 	 * core when the resolution code could fetch entries off disk.
189 	 */
190 	if (hook != NULLHOOK && (*hook)(&thisentry))
191 	    immediate++;
192 	else
193 	    enqueue(&thisentry);
194     }
195 
196     if (_nc_tail)
197     {
198 	/* set up the head pointer */
199 	for (_nc_head = _nc_tail; _nc_head->last; _nc_head = _nc_head->last)
200 	    continue;
201 
202 	DEBUG(1, ("head = %s", _nc_head->tterm.term_names));
203 	DEBUG(1, ("tail = %s", _nc_tail->tterm.term_names));
204     }
205 #ifdef TRACE
206     else if (!immediate)
207 	DEBUG(1, ("no entries parsed"));
208 #endif
209 
210     _nc_suppress_warnings = oldsuppress;
211 }
212 
213 int _nc_resolve_uses(void)
214 /* try to resolve all use capabilities */
215 {
216     ENTRY	*qp, *rp, *lastread = NULL;
217     bool	keepgoing;
218     int		i, j, unresolved, total_unresolved, multiples;
219 
220     DEBUG(2, ("RESOLUTION BEGINNING"));
221 
222     /*
223      * Check for multiple occurrences of the same name.
224      */
225     multiples = 0;
226     for_entry_list(qp)
227     {
228 	int matchcount = 0;
229 
230 	for_entry_list(rp)
231 	    if (qp > rp
232 		&& _nc_entry_match(qp->tterm.term_names, rp->tterm.term_names))
233 	    {
234 		matchcount++;
235 		if (matchcount == 1)
236 		{
237 		    (void) fprintf(stderr, "Name collision between %s",
238 			   _nc_first_name(qp->tterm.term_names));
239 		    multiples++;
240 		}
241 		if (matchcount >= 1)
242 		    (void) fprintf(stderr, " %s", _nc_first_name(rp->tterm.term_names));
243 	    }
244 	if (matchcount >= 1)
245 	    (void) putc('\n', stderr);
246     }
247     if (multiples > 0)
248 	return(FALSE);
249 
250     DEBUG(2, ("NO MULTIPLE NAME OCCURRENCES"));
251 
252     /*
253      * First resolution stage: replace names in use arrays with entry
254      * pointers.  By doing this, we avoid having to do the same name
255      * match once for each time a use entry is itself unresolved.
256      */
257     total_unresolved = 0;
258     _nc_curr_col = -1;
259     for_entry_list(qp)
260     {
261 	unresolved = 0;
262 	for (i = 0; i < qp->nuses; i++)
263 	{
264 	    bool	foundit;
265 	    char	*child = _nc_first_name(qp->tterm.term_names);
266 	    char	*lookfor = (char *)(qp->uses[i].parent);
267 	    long	lookline = qp->uses[i].line;
268 
269 	    foundit = FALSE;
270 
271 	    _nc_set_type(child);
272 
273 	    /* first, try to resolve from in-core records */
274 	    for_entry_list(rp)
275 		if (rp != qp
276 		    && _nc_name_match(rp->tterm.term_names, lookfor, "|"))
277 		{
278 		    DEBUG(2, ("%s: resolving use=%s (in core)",
279 			      child, lookfor));
280 
281 		    qp->uses[i].parent = rp;
282 		    foundit = TRUE;
283 		}
284 
285 	    /* if that didn't work, try to merge in a compiled entry */
286 	    if (!foundit)
287 	    {
288 		TERMTYPE	thisterm;
289 		char		filename[PATH_MAX];
290 
291 		if (_nc_read_entry(lookfor, filename, &thisterm) == 1)
292 		{
293 		    DEBUG(2, ("%s: resolving use=%s (compiled)",
294 			      child, lookfor));
295 
296 		    rp = (ENTRY *)malloc(sizeof(ENTRY));
297 		    if (rp == NULL)
298 			_nc_err_abort("Out of memory");
299 		    memcpy(&rp->tterm, &thisterm, sizeof(TERMTYPE));
300 		    rp->nuses = 0;
301 		    rp->next = lastread;
302 		    lastread = rp;
303 
304 		    qp->uses[i].parent = rp;
305 		    foundit = TRUE;
306 		}
307 	    }
308 
309 	    /* no good, mark this one unresolvable and complain */
310 	    if (!foundit)
311 	    {
312 		unresolved++;
313 		total_unresolved++;
314 
315 		_nc_curr_line = lookline;
316 		_nc_warning("resolution of use=%s failed", lookfor);
317 		qp->uses[i].parent = (ENTRY *)NULL;
318 	    }
319 	}
320     }
321     if (total_unresolved)
322     {
323 	/* free entries read in off disk */
324 	_nc_free_entries(lastread);
325 	return(FALSE);
326     }
327 
328     DEBUG(2, ("NAME RESOLUTION COMPLETED OK"));
329 
330     /*
331      * OK, at this point all (char *) references have been successfully
332      * replaced by (ENTRY *) pointers.  Time to do the actual merges.
333      */
334     do {
335 	TERMTYPE	merged;
336 
337 	keepgoing = FALSE;
338 
339 	for_entry_list(qp)
340 	    if (qp->nuses > 0)
341 	    {
342 		DEBUG(2, ("%s: attempting merge", _nc_first_name(qp->tterm.term_names)));
343 		/*
344 		 * If any of the use entries we're looking for is
345 		 * incomplete, punt.  We'll catch this entry on a
346 		 * subsequent pass.
347 		 */
348 		for (i = 0; i < qp->nuses; i++)
349 		    if (((ENTRY *)qp->uses[i].parent)->nuses)
350 		    {
351 			DEBUG(2, ("%s: use entry %d unresolved",
352 				  _nc_first_name(qp->tterm.term_names), i));
353 			goto incomplete;
354 		    }
355 
356 		/*
357 		 * First, make sure there's no garbage in the merge block.
358 		 * as a side effect, copy into the merged entry the name
359 		 * field and string table pointer.
360 		 */
361 		memcpy(&merged, &qp->tterm, sizeof(TERMTYPE));
362 
363 		/*
364 		 * Now merge in each use entry in the proper
365 		 * (reverse) order.
366 		 */
367 		for (; qp->nuses; qp->nuses--)
368 		    _nc_merge_entry(&merged,
369 				&((ENTRY *)qp->uses[qp->nuses-1].parent)->tterm);
370 
371 		/*
372 		 * Now merge in the original entry.
373 		 */
374 		_nc_merge_entry(&merged, &qp->tterm);
375 
376 		/*
377 		 * Replace the original entry with the merged one.
378 		 */
379 		memcpy(&qp->tterm, &merged, sizeof(TERMTYPE));
380 
381 		/*
382 		 * We know every entry is resolvable because name resolution
383 		 * didn't bomb.  So go back for another pass.
384 		 */
385 		/* FALLTHRU */
386 	    incomplete:
387 		keepgoing = TRUE;
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 (j = 0; j < BOOLCOUNT; j++)
402 	    if (qp->tterm.Booleans[j] == CANCELLED_BOOLEAN)
403 		qp->tterm.Booleans[j] = FALSE;
404 	for (j = 0; j < NUMCOUNT; j++)
405 	    if (qp->tterm.Numbers[j] == CANCELLED_NUMERIC)
406 		qp->tterm.Numbers[j] = ABSENT_NUMERIC;
407 	for (j = 0; j < STRCOUNT; j++)
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     _nc_curr_col = -1;
422     for_entry_list(qp)
423     {
424 	_nc_curr_line = qp->startline;
425 	_nc_set_type(_nc_first_name(qp->tterm.term_names));
426 	sanity_check(&qp->tterm);
427     }
428 
429     DEBUG(2, ("SANITY CHECK FINISHED"));
430 
431     return(TRUE);
432 }
433 
434 /*
435  * This bit of legerdemain turns all the terminfo variable names into
436  * references to locations in the arrays Booleans, Numbers, and Strings ---
437  * precisely what's needed.
438  */
439 
440 #undef CUR
441 #define CUR tp->
442 
443 /*
444  * Note that WANTED and PRESENT are not simple inverses!  If a capability
445  * has been explicitly cancelled, it's not considered WANTED.
446  */
447 #define WANTED(s)	((s) == ABSENT_STRING)
448 #define PRESENT(s)	(((s) != ABSENT_STRING) && ((s) != CANCELLED_STRING))
449 
450 #define ANDMISSING(p,q) \
451 		{if (PRESENT(p) && !PRESENT(q)) _nc_warning(#p " but no " #q);}
452 
453 #define PAIRED(p,q) \
454 		{ \
455 		if (PRESENT(q) && !PRESENT(p)) \
456 			_nc_warning(#q " but no " #p); \
457 		if (PRESENT(p) && !PRESENT(q)) \
458 			_nc_warning(#p " but no " #q); \
459 		}
460 
461 static void sanity_check(TERMTYPE *tp)
462 {
463 #ifdef __UNUSED__	/* this casts too wide a net */
464     bool       terminal_entry = !strchr(tp->term_names, '+');
465 #endif
466 
467     if (!PRESENT(exit_attribute_mode))
468     {
469 #ifdef __UNUSED__	/* this casts too wide a net */
470 	if (terminal_entry &&
471 		(PRESENT(set_attributes)
472 		|| PRESENT(enter_standout_mode)
473 		|| PRESENT(enter_underline_mode)
474 		|| PRESENT(enter_blink_mode)
475 		|| PRESENT(enter_bold_mode)
476 		|| PRESENT(enter_dim_mode)
477 		|| PRESENT(enter_secure_mode)
478 		|| PRESENT(enter_protected_mode)
479 		|| PRESENT(enter_reverse_mode)))
480 	    _nc_warning("no exit_attribute_mode");
481 #endif /* __UNUSED__ */
482 	PAIRED(enter_standout_mode,     exit_standout_mode)
483 	PAIRED(enter_underline_mode,    exit_underline_mode)
484     }
485 
486      /* listed in structure-member order of first argument */
487 #ifdef __UNUSED__
488      ANDMISSING(cursor_invisible,            cursor_normal)
489      ANDMISSING(cursor_visible,              cursor_normal)
490 #endif /* __UNUSED__ */
491      PAIRED(enter_alt_charset_mode,          exit_alt_charset_mode)
492      ANDMISSING(enter_alt_charset_mode,      acs_chars)
493      ANDMISSING(exit_alt_charset_mode,       acs_chars)
494      ANDMISSING(enter_blink_mode,            exit_attribute_mode)
495      ANDMISSING(enter_bold_mode,             exit_attribute_mode)
496      PAIRED(exit_ca_mode,                    enter_ca_mode)
497      PAIRED(enter_delete_mode,               exit_delete_mode)
498      ANDMISSING(enter_dim_mode,              exit_attribute_mode)
499      PAIRED(enter_insert_mode,               exit_insert_mode)
500      ANDMISSING(enter_secure_mode,           exit_attribute_mode)
501      ANDMISSING(enter_protected_mode,        exit_attribute_mode)
502      ANDMISSING(enter_reverse_mode,          exit_attribute_mode)
503      PAIRED(from_status_line,                to_status_line)
504      PAIRED(meta_off,                        meta_on)
505 
506      PAIRED(prtr_on,                         prtr_off)
507      PAIRED(save_cursor,                     restore_cursor)
508      PAIRED(enter_xon_mode,                  exit_xon_mode)
509      PAIRED(enter_am_mode,                   exit_am_mode)
510      ANDMISSING(label_off,                   label_on)
511      PAIRED(display_clock,                   remove_clock)
512      ANDMISSING(set_color_pair,              initialize_pair)
513 
514      /* Some checks that we should make, but don't want to confuse people
515       * with.  Put those under the tic -v option so we can still get them.
516       */
517      if (_nc_tracing) {
518 
519 	/*
520 	 * From XSI & O'Reilly, we gather that sc/rc are required if csr is
521 	 * given, because the cursor position after the scrolling operation is
522 	 * performed is undefined.
523 	 */
524          ANDMISSING(change_scroll_region,        save_cursor)
525          ANDMISSING(change_scroll_region,        restore_cursor)
526 
527          /*
528 	  * Some non-curses applications (e.g., jove) get confused if we have
529 	  * both ich/ich1 and smir/rmir.  Let's be nice and warn about that,
530 	  * too, even though ncurses handles it.
531           */
532          if ((PRESENT(enter_insert_mode) || PRESENT(exit_insert_mode))
533           && (PRESENT(insert_character)  || PRESENT(parm_ich))) {
534 	    _nc_warning("non-curses applications may be confused by ich/ich1 with smir/rmir");
535          }
536      }
537 #undef PAIRED
538 #undef ANDMISSING
539 }
540