xref: /openbsd/lib/libcurses/tinfo/comp_scan.c (revision 379777c0)
1 /* $OpenBSD: comp_scan.c,v 1.16 2023/10/17 09:52:09 nicm Exp $ */
2 
3 /****************************************************************************
4 ,* Copyright 2020-2022,2023 Thomas E. Dickey                                *
5  * Copyright 1998-2016,2017 Free Software Foundation, Inc.                  *
6  *                                                                          *
7  * Permission is hereby granted, free of charge, to any person obtaining a  *
8  * copy of this software and associated documentation files (the            *
9  * "Software"), to deal in the Software without restriction, including      *
10  * without limitation the rights to use, copy, modify, merge, publish,      *
11  * distribute, distribute with modifications, sublicense, and/or sell       *
12  * copies of the Software, and to permit persons to whom the Software is    *
13  * furnished to do so, subject to the following conditions:                 *
14  *                                                                          *
15  * The above copyright notice and this permission notice shall be included  *
16  * in all copies or substantial portions of the Software.                   *
17  *                                                                          *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
21  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
22  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
23  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
24  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
25  *                                                                          *
26  * Except as contained in this notice, the name(s) of the above copyright   *
27  * holders shall not be used in advertising or otherwise to promote the     *
28  * sale, use or other dealings in this Software without prior written       *
29  * authorization.                                                           *
30  ****************************************************************************/
31 
32 /****************************************************************************
33  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
34  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
35  *     and: Thomas E. Dickey                        1996 on                 *
36  ****************************************************************************/
37 
38 /*
39  *	comp_scan.c --- Lexical scanner for terminfo compiler.
40  *
41  *	_nc_reset_input()
42  *	_nc_get_token()
43  *	_nc_panic_mode()
44  *	int _nc_syntax;
45  *	int _nc_curr_line;
46  *	long _nc_curr_file_pos;
47  *	long _nc_comment_start;
48  *	long _nc_comment_end;
49  */
50 
51 #include <curses.priv.h>
52 
53 #include <ctype.h>
54 #include <tic.h>
55 
56 MODULE_ID("$Id: comp_scan.c,v 1.16 2023/10/17 09:52:09 nicm Exp $")
57 
58 /*
59  * Maximum length of string capability we'll accept before raising an error.
60  * Yes, there is a real capability in /etc/termcap this long, an "is".
61  */
62 #define MAXCAPLEN	600
63 
64 #define iswhite(ch)	(ch == ' '  ||  ch == '\t')
65 
66 NCURSES_EXPORT_VAR (int) _nc_syntax = 0;         /* termcap or terminfo? */
67 NCURSES_EXPORT_VAR (int) _nc_strict_bsd = 1;  /* ncurses extended termcap? */
68 NCURSES_EXPORT_VAR (long) _nc_curr_file_pos = 0; /* file offset of current line */
69 NCURSES_EXPORT_VAR (long) _nc_comment_start = 0; /* start of comment range before name */
70 NCURSES_EXPORT_VAR (long) _nc_comment_end = 0;   /* end of comment range before name */
71 NCURSES_EXPORT_VAR (long) _nc_start_line = 0;    /* start line of current entry */
72 
73 NCURSES_EXPORT_VAR (struct token) _nc_curr_token =
74 {
75     0, 0, 0
76 };
77 
78 /*****************************************************************************
79  *
80  * Token-grabbing machinery
81  *
82  *****************************************************************************/
83 
84 static bool first_column;	/* See 'next_char()' below */
85 static bool had_newline;
86 static char separator;		/* capability separator */
87 static int pushtype;		/* type of pushback token */
88 static char *pushname;
89 
90 #if NCURSES_EXT_FUNCS
91 NCURSES_EXPORT_VAR (bool) _nc_disable_period = FALSE; /* used by tic -a option */
92 #endif
93 
94 /*****************************************************************************
95  *
96  * Character-stream handling
97  *
98  *****************************************************************************/
99 
100 #define LEXBUFSIZ	1024
101 
102 static char *bufptr;		/* otherwise, the input buffer pointer */
103 static char *bufstart;		/* start of buffer so we can compute offsets */
104 static FILE *yyin;		/* scanner's input file descriptor */
105 
106 /*
107  *	_nc_reset_input()
108  *
109  *	Resets the input-reading routines.  Used on initialization,
110  *	or after a seek has been done.  Exactly one argument must be
111  *	non-null.
112  */
113 
114 NCURSES_EXPORT(void)
115 _nc_reset_input(FILE *fp, char *buf)
116 {
117     TR(TRACE_DATABASE,
118        (T_CALLED("_nc_reset_input(fp=%p, buf=%p)"), (void *) fp, buf));
119 
120     pushtype = NO_PUSHBACK;
121     if (pushname != 0)
122 	pushname[0] = '\0';
123     yyin = fp;
124     bufstart = bufptr = buf;
125     _nc_curr_file_pos = 0L;
126     if (fp != 0)
127 	_nc_curr_line = 0;
128     _nc_curr_col = 0;
129 
130     returnVoidDB;
131 }
132 
133 /*
134  *	int last_char()
135  *
136  *	Returns the final nonblank character on the current input buffer
137  */
138 static int
139 last_char(int from_end)
140 {
141     size_t len = strlen(bufptr);
142     int result = 0;
143 
144     while (len--) {
145 	if (!isspace(UChar(bufptr[len]))) {
146 	    if (from_end <= (int) len)
147 		result = bufptr[(int) len - from_end];
148 	    break;
149 	}
150     }
151     return result;
152 }
153 
154 /*
155  * Read, like fgets(), but error-out if the input contains nulls.
156  */
157 static int
158 get_text(char *buffer, int length)
159 {
160     int count = 0;
161     int limit = length - 1;
162 
163     while (limit-- > 0) {
164 	int ch = fgetc(yyin);
165 
166 	if (ch == '\0') {
167 	    _nc_err_abort("This is not a text-file");
168 	} else if (ch == EOF) {
169 	    break;
170 	}
171 	++count;
172 	*buffer++ = (char) ch;
173 	if (ch == '\n')
174 	    break;
175     }
176     *buffer = '\0';
177     return count;
178 }
179 
180 /*
181  *	int next_char()
182  *
183  *	Returns the next character in the input stream.  Comments and leading
184  *	white space are stripped.
185  *
186  *	The global state variable 'firstcolumn' is set TRUE if the character
187  *	returned is from the first column of the input line.
188  *
189  *	The global variable _nc_curr_line is incremented for each new line.
190  *	The global variable _nc_curr_file_pos is set to the file offset of the
191  *	beginning of each line.
192  */
193 
194 static int
195 next_char(void)
196 {
197     static char *result;
198     static size_t allocated;
199     int the_char;
200 
201     if (!yyin) {
202 	if (result != 0) {
203 	    FreeAndNull(result);
204 	    FreeAndNull(pushname);
205 	    bufptr = 0;
206 	    bufstart = 0;
207 	    allocated = 0;
208 	}
209 	/*
210 	 * An string with an embedded null will truncate the input.  This is
211 	 * intentional (we don't read binary files here).
212 	 */
213 	if (bufptr == 0 || *bufptr == '\0')
214 	    return (EOF);
215 	if (*bufptr == '\n') {
216 	    _nc_curr_line++;
217 	    _nc_curr_col = 0;
218 	} else if (*bufptr == '\t') {
219 	    _nc_curr_col = (_nc_curr_col | 7);
220 	}
221     } else if (!bufptr || !*bufptr) {
222 	/*
223 	 * In theory this could be recoded to do its I/O one character at a
224 	 * time, saving the buffer space.  In practice, this turns out to be
225 	 * quite hard to get completely right.  Try it and see.  If you
226 	 * succeed, don't forget to hack push_back() correspondingly.
227 	 */
228 	size_t len;
229 
230 	do {
231 	    size_t used = 0;
232 	    bufstart = 0;
233 	    do {
234 		if (used + (LEXBUFSIZ / 4) >= allocated) {
235 		    allocated += (allocated + LEXBUFSIZ);
236 		    result = typeRealloc(char, allocated, result);
237 		    if (result == 0)
238 			return (EOF);
239 		    if (bufstart)
240 			bufstart = result;
241 		}
242 		if (used == 0)
243 		    _nc_curr_file_pos = ftell(yyin);
244 
245 		if (get_text(result + used, (int) (allocated - used))) {
246 		    bufstart = result;
247 		    if (used == 0) {
248 			if (_nc_curr_line == 0
249 			    && IS_TIC_MAGIC(result)) {
250 			    _nc_err_abort("This is a compiled terminal description, not a source");
251 			}
252 			_nc_curr_line++;
253 			_nc_curr_col = 0;
254 		    }
255 		} else {
256 		    if (used != 0)
257 			_nc_STRCAT(result, "\n", allocated);
258 		}
259 		if ((bufptr = bufstart) != 0) {
260 		    used = strlen(bufptr);
261 		    if (used == 0)
262 			return (EOF);
263 		    while (iswhite(*bufptr)) {
264 			if (*bufptr == '\t') {
265 			    _nc_curr_col = (_nc_curr_col | 7) + 1;
266 			} else {
267 			    _nc_curr_col++;
268 			}
269 			bufptr++;
270 		    }
271 
272 		    /*
273 		     * Treat a trailing <cr><lf> the same as a <newline> so we
274 		     * can read files on OS/2, etc.
275 		     */
276 		    if ((len = strlen(bufptr)) > 1) {
277 			if (bufptr[len - 1] == '\n'
278 			    && bufptr[len - 2] == '\r') {
279 			    len--;
280 			    bufptr[len - 1] = '\n';
281 			    bufptr[len] = '\0';
282 			}
283 		    }
284 		} else {
285 		    return (EOF);
286 		}
287 	    } while (bufptr[len - 1] != '\n');	/* complete a line */
288 	} while (result[0] == '#');	/* ignore comments */
289     } else if (*bufptr == '\t') {
290 	_nc_curr_col = (_nc_curr_col | 7);
291     }
292 
293     first_column = (bufptr == bufstart);
294     if (first_column)
295 	had_newline = FALSE;
296 
297     _nc_curr_col++;
298     the_char = *bufptr++;
299     return UChar(the_char);
300 }
301 
302 static void
303 push_back(int c)
304 /* push a character back onto the input stream */
305 {
306     if (bufptr == bufstart)
307 	_nc_syserr_abort("cannot backspace off beginning of line");
308     *--bufptr = (char) c;
309     _nc_curr_col--;
310 }
311 
312 static long
313 stream_pos(void)
314 /* return our current character position in the input stream */
315 {
316     return (yyin ? ftell(yyin) : (bufptr ? (long) (bufptr - bufstart) : 0));
317 }
318 
319 static bool
320 end_of_stream(void)
321 /* are we at end of input? */
322 {
323     return ((yyin
324 	     ? (feof(yyin) && (bufptr == NULL || *bufptr == '\0'))
325 	     : (bufptr && *bufptr == '\0'))
326 	    ? TRUE : FALSE);
327 }
328 
329 /* Assume we may be looking at a termcap-style continuation */
330 static NCURSES_INLINE int
331 eat_escaped_newline(int ch)
332 {
333     if (ch == '\\') {
334 	while ((ch = next_char()) == '\n' || iswhite(ch)) {
335 	    /* EMPTY */ ;
336 	}
337     }
338     return ch;
339 }
340 
341 #define TOK_BUF_SIZE MAX_ENTRY_SIZE
342 
343 #define OkToAdd() \
344 	((tok_ptr - tok_buf) < (TOK_BUF_SIZE - 2))
345 
346 #define AddCh(ch) \
347 	*tok_ptr++ = (char) ch; \
348 	*tok_ptr = '\0'
349 
350 static char *tok_buf;
351 
352 /*
353  *	int
354  *	get_token()
355  *
356  *	Scans the input for the next token, storing the specifics in the
357  *	global structure 'curr_token' and returning one of the following:
358  *
359  *		NAMES		A line beginning in column 1.  'name'
360  *				will be set to point to everything up to but
361  *				not including the first separator on the line.
362  *		BOOLEAN		An entry consisting of a name followed by
363  *				a separator.  'name' will be set to point to
364  *				the name of the capability.
365  *		NUMBER		An entry of the form
366  *					name#digits,
367  *				'name' will be set to point to the capability
368  *				name and 'valnumber' to the number given.
369  *		STRING		An entry of the form
370  *					name=characters,
371  *				'name' is set to the capability name and
372  *				'valstring' to the string of characters, with
373  *				input translations done.
374  *		CANCEL		An entry of the form
375  *					name@,
376  *				'name' is set to the capability name and
377  *				'valnumber' to -1.
378  *		EOF		The end of the file has been reached.
379  *
380  *	A `separator' is either a comma or a semicolon, depending on whether
381  *	we are in termcap or terminfo mode.
382  *
383  */
384 
385 NCURSES_EXPORT(int)
386 _nc_get_token(bool silent)
387 {
388     static const char terminfo_punct[] = "@%&*!#";
389 
390     char *after_name;		/* after primary name */
391     char *after_list;		/* after primary and alias list */
392     char *numchk;
393     char *tok_ptr;
394     char *s;
395     char numbuf[80];
396     int ch, c0, c1;
397     int dot_flag = FALSE;
398     int type;
399     long number;
400     long token_start;
401     unsigned found;
402 #ifdef TRACE
403     int old_line;
404     int old_col;
405 #endif
406 
407     DEBUG(3, (T_CALLED("_nc_get_token(silent=%d)"), silent));
408 
409     if (pushtype != NO_PUSHBACK) {
410 	int retval = pushtype;
411 
412 	_nc_set_type(pushname != 0 ? pushname : "");
413 	DEBUG(3, ("pushed-back token: `%s', class %d",
414 		  _nc_curr_token.tk_name, pushtype));
415 
416 	pushtype = NO_PUSHBACK;
417 	if (pushname != 0)
418 	    pushname[0] = '\0';
419 
420 	/* currtok wasn't altered by _nc_push_token() */
421 	DEBUG(3, (T_RETURN("%d"), retval));
422 	return (retval);
423     }
424 
425     if (end_of_stream()) {
426 	yyin = 0;
427 	(void) next_char();	/* frees its allocated memory */
428 	if (tok_buf != 0) {
429 	    if (_nc_curr_token.tk_name == tok_buf)
430 		_nc_curr_token.tk_name = 0;
431 	}
432 	DEBUG(3, (T_RETURN("%d"), EOF));
433 	return (EOF);
434     }
435 
436   start_token:
437     token_start = stream_pos();
438     while ((ch = next_char()) == '\n' || iswhite(ch)) {
439 	if (ch == '\n')
440 	    had_newline = TRUE;
441     }
442 
443     ch = eat_escaped_newline(ch);
444     _nc_curr_token.tk_valstring = 0;
445 
446 #ifdef TRACE
447     old_line = _nc_curr_line;
448     old_col = _nc_curr_col;
449 #endif
450     if (ch == EOF)
451 	type = EOF;
452     else {
453 	/* if this is a termcap entry, skip a leading separator */
454 	if (separator == ':' && ch == ':')
455 	    ch = next_char();
456 
457 	if (ch == '.'
458 #if NCURSES_EXT_FUNCS
459 	    && !_nc_disable_period
460 #endif
461 	    ) {
462 	    dot_flag = TRUE;
463 	    DEBUG(8, ("dot-flag set"));
464 
465 	    while ((ch = next_char()) == '.' || iswhite(ch)) {
466 		/* EMPTY */ ;
467 	    }
468 	}
469 
470 	if (ch == EOF) {
471 	    type = EOF;
472 	    goto end_of_token;
473 	}
474 
475 	/* have to make some punctuation chars legal for terminfo */
476 	if (!isalnum(UChar(ch))
477 #if NCURSES_EXT_FUNCS
478 	    && !(ch == '.' && _nc_disable_period)
479 #endif
480 	    && ((strchr) (terminfo_punct, (char) ch) == 0)) {
481 	    if (!silent)
482 		_nc_warning("Illegal character (expected alphanumeric or %s) - '%s'",
483 			    terminfo_punct, unctrl(UChar(ch)));
484 	    _nc_panic_mode(separator);
485 	    goto start_token;
486 	}
487 
488 	if (tok_buf == 0)
489 	    tok_buf = typeMalloc(char, TOK_BUF_SIZE);
490 
491 #ifdef TRACE
492 	old_line = _nc_curr_line;
493 	old_col = _nc_curr_col;
494 #endif
495 	tok_ptr = tok_buf;
496 	AddCh(ch);
497 
498 	if (first_column) {
499 	    _nc_comment_start = token_start;
500 	    _nc_comment_end = _nc_curr_file_pos;
501 	    _nc_start_line = _nc_curr_line;
502 
503 	    _nc_syntax = ERR;
504 	    after_name = 0;
505 	    after_list = 0;
506 	    while ((ch = next_char()) != '\n') {
507 		if (ch == EOF) {
508 		    _nc_err_abort(MSG_NO_INPUTS);
509 		} else if (ch == '|') {
510 		    after_list = tok_ptr;
511 		    if (after_name == 0)
512 			after_name = tok_ptr;
513 		} else if (ch == ':' && last_char(0) != ',') {
514 		    _nc_syntax = SYN_TERMCAP;
515 		    separator = ':';
516 		    break;
517 		} else if (ch == ',') {
518 		    _nc_syntax = SYN_TERMINFO;
519 		    separator = ',';
520 		    /*
521 		     * If we did not see a '|', then we found a name with no
522 		     * aliases or description.
523 		     */
524 		    if (after_name == 0)
525 			break;
526 		    /*
527 		     * We saw a comma, but are not entirely sure this is
528 		     * terminfo format, since we can still be parsing the
529 		     * description field (for either syntax).
530 		     *
531 		     * A properly formatted termcap line ends with either a
532 		     * colon, or a backslash after a colon.  It is possible
533 		     * to have a backslash in the middle of a capability, but
534 		     * then there would be no leading whitespace on the next
535 		     * line - something we want to discourage.
536 		     */
537 		    c0 = last_char(0);
538 		    c1 = last_char(1);
539 		    if (c1 != ':' && c0 != '\\' && c0 != ':') {
540 			bool capability = FALSE;
541 
542 			/*
543 			 * Since it is not termcap, assume the line is terminfo
544 			 * format.  However, the comma can be embedded in a
545 			 * description field.  It also can be a separator
546 			 * between a description field and a capability.
547 			 *
548 			 * Improve the guess by checking if the next word after
549 			 * the comma does not look like a capability.  In that
550 			 * case, extend the description past the comma.
551 			 */
552 			for (s = bufptr; isspace(UChar(*s)); ++s) {
553 			    ;
554 			}
555 			if (islower(UChar(*s))) {
556 			    char *name = s;
557 			    while (isalnum(UChar(*s))) {
558 				++s;
559 			    }
560 			    if (*s == '#' || *s == '=' || *s == '@') {
561 				/*
562 				 * Checking solely with syntax allows us to
563 				 * support extended capabilities with string
564 				 * values.
565 				 */
566 				capability = TRUE;
567 			    } else if (*s == ',') {
568 				c0 = *s;
569 				*s = '\0';
570 				/*
571 				 * Otherwise, we can handle predefined boolean
572 				 * capabilities, still aided by syntax.
573 				 */
574 				if (_nc_find_entry(name,
575 						   _nc_get_hash_table(FALSE))) {
576 				    capability = TRUE;
577 				}
578 				*s = (char) c0;
579 			    }
580 			}
581 			if (capability) {
582 			    break;
583 			}
584 		    }
585 		} else
586 		    ch = eat_escaped_newline(ch);
587 
588 		if (OkToAdd()) {
589 		    AddCh(ch);
590 		} else {
591 		    break;
592 		}
593 	    }
594 	    *tok_ptr = '\0';
595 	    if (_nc_syntax == ERR) {
596 		/*
597 		 * Grrr...what we ought to do here is barf, complaining that
598 		 * the entry is malformed.  But because a couple of name fields
599 		 * in the 8.2 termcap file end with |\, we just have to assume
600 		 * it is termcap syntax.
601 		 */
602 		_nc_syntax = SYN_TERMCAP;
603 		separator = ':';
604 	    } else if (_nc_syntax == SYN_TERMINFO) {
605 		/* throw away trailing /, *$/ */
606 		for (--tok_ptr;
607 		     iswhite(*tok_ptr) || *tok_ptr == ',';
608 		     tok_ptr--) {
609 		    /* EMPTY */ ;
610 		}
611 		tok_ptr[1] = '\0';
612 	    }
613 
614 	    /*
615 	     * This is the soonest we have the terminal name fetched.  Set up
616 	     * for following warning messages.  If there's no '|', then there
617 	     * is no description.
618 	     */
619 	    if (after_name != 0) {
620 		ch = *after_name;
621 		*after_name = '\0';
622 		_nc_set_type(tok_buf);
623 		*after_name = (char) ch;
624 	    }
625 
626 	    /*
627 	     * Compute the boundary between the aliases and the description
628 	     * field for syntax-checking purposes.
629 	     */
630 	    if (after_list != 0) {
631 		if (!silent) {
632 		    if (*after_list == '\0' || strchr("|", after_list[1]) != NULL) {
633 			_nc_warning("empty longname field");
634 		    } else if (strchr(after_list, ' ') == 0) {
635 			_nc_warning("older tic versions may treat the description field as an alias");
636 		    }
637 		}
638 	    } else {
639 		after_list = tok_buf + strlen(tok_buf);
640 		DEBUG(2, ("missing description"));
641 	    }
642 
643 	    /*
644 	     * Whitespace in a name field other than the long name can confuse
645 	     * rdist and some termcap tools.  Slashes are a no-no.  Other
646 	     * special characters can be dangerous due to shell expansion.
647 	     */
648 	    for (s = tok_buf; s < after_list; ++s) {
649 		if (isspace(UChar(*s))) {
650 		    if (!silent)
651 			_nc_warning("whitespace in name or alias field");
652 		    break;
653 		} else if (*s == '/') {
654 		    if (!silent)
655 			_nc_warning("slashes aren't allowed in names or aliases");
656 		    break;
657 		} else if (strchr("$[]!*?", *s)) {
658 		    if (!silent)
659 			_nc_warning("dubious character `%c' in name or alias field", *s);
660 		    break;
661 		}
662 	    }
663 
664 	    _nc_curr_token.tk_name = tok_buf;
665 	    type = NAMES;
666 	} else {
667 	    if (had_newline && _nc_syntax == SYN_TERMCAP) {
668 		_nc_warning("Missing backslash before newline");
669 		had_newline = FALSE;
670 	    }
671 	    while ((ch = next_char()) != EOF) {
672 		if (!isalnum(UChar(ch))) {
673 		    if (_nc_syntax == SYN_TERMINFO) {
674 			if (ch != '_')
675 			    break;
676 		    } else {	/* allow ';' for "k;" */
677 			if (ch != ';')
678 			    break;
679 		    }
680 		}
681 		if (OkToAdd()) {
682 		    AddCh(ch);
683 		} else {
684 		    ch = EOF;
685 		    break;
686 		}
687 	    }
688 
689 	    *tok_ptr++ = '\0';	/* separate name/value in buffer */
690 	    switch (ch) {
691 	    case ',':
692 	    case ':':
693 		if (ch != separator)
694 		    _nc_err_abort("Separator inconsistent with syntax");
695 		_nc_curr_token.tk_name = tok_buf;
696 		type = BOOLEAN;
697 		break;
698 	    case '@':
699 		if ((ch = next_char()) != separator && !silent)
700 		    _nc_warning("Missing separator after `%s', have %s",
701 				tok_buf, unctrl(UChar(ch)));
702 		_nc_curr_token.tk_name = tok_buf;
703 		type = CANCEL;
704 		break;
705 
706 	    case '#':
707 		found = 0;
708 		while (isalnum(ch = next_char())) {
709 		    numbuf[found++] = (char) ch;
710 		    if (found >= sizeof(numbuf) - 1)
711 			break;
712 		}
713 		numbuf[found] = '\0';
714 		number = strtol(numbuf, &numchk, 0);
715 		if (!silent) {
716 		    if (numchk == numbuf)
717 			_nc_warning("no value given for `%s'", tok_buf);
718 		    if ((*numchk != '\0') || (ch != separator))
719 			_nc_warning("Missing separator for `%s'", tok_buf);
720 		    if (number < 0)
721 			_nc_warning("value of `%s' cannot be negative", tok_buf);
722 		    if (number > MAX_OF_TYPE(NCURSES_INT2)) {
723 			_nc_warning("limiting value of `%s' from %#lx to %#x",
724 				    tok_buf,
725 				    number, MAX_OF_TYPE(NCURSES_INT2));
726 			number = MAX_OF_TYPE(NCURSES_INT2);
727 		    }
728 		}
729 		_nc_curr_token.tk_name = tok_buf;
730 		_nc_curr_token.tk_valnumber = (int) number;
731 		type = NUMBER;
732 		break;
733 
734 	    case '=':
735 		ch = _nc_trans_string(tok_ptr, tok_buf + TOK_BUF_SIZE);
736 		if (!silent && ch != separator)
737 		    _nc_warning("Missing separator");
738 		_nc_curr_token.tk_name = tok_buf;
739 		_nc_curr_token.tk_valstring = tok_ptr;
740 		type = STRING;
741 		break;
742 
743 	    case EOF:
744 		type = EOF;
745 		break;
746 	    default:
747 		/* just to get rid of the compiler warning */
748 		type = UNDEF;
749 		if (!silent)
750 		    _nc_warning("Illegal character - '%s'", unctrl(UChar(ch)));
751 	    }
752 	}			/* end else (first_column == FALSE) */
753     }				/* end else (ch != EOF) */
754 
755   end_of_token:
756 
757 #ifdef TRACE
758     if (dot_flag == TRUE)
759 	DEBUG(8, ("Commented out "));
760 
761     if (_nc_tracing >= DEBUG_LEVEL(8)) {
762 	_tracef("parsed %d.%d to %d.%d",
763 		old_line, old_col,
764 		_nc_curr_line, _nc_curr_col);
765     }
766     if (_nc_tracing >= DEBUG_LEVEL(7)) {
767 	switch (type) {
768 	case BOOLEAN:
769 	    _tracef("Token: Boolean; name='%s'",
770 		    _nc_curr_token.tk_name);
771 	    break;
772 
773 	case NUMBER:
774 	    _tracef("Token: Number;  name='%s', value=%d",
775 		    _nc_curr_token.tk_name,
776 		    _nc_curr_token.tk_valnumber);
777 	    break;
778 
779 	case STRING:
780 	    _tracef("Token: String;  name='%s', value=%s",
781 		    _nc_curr_token.tk_name,
782 		    _nc_visbuf(_nc_curr_token.tk_valstring));
783 	    break;
784 
785 	case CANCEL:
786 	    _tracef("Token: Cancel; name='%s'",
787 		    _nc_curr_token.tk_name);
788 	    break;
789 
790 	case NAMES:
791 
792 	    _tracef("Token: Names; value='%s'",
793 		    _nc_curr_token.tk_name);
794 	    break;
795 
796 	case EOF:
797 	    _tracef("Token: End of file");
798 	    break;
799 
800 	default:
801 	    _nc_warning("Bad token type");
802 	}
803     }
804 #endif
805 
806     if (dot_flag == TRUE)	/* if commented out, use the next one */
807 	type = _nc_get_token(silent);
808 
809     DEBUG(3, ("token: `%s', class %d",
810 	      ((_nc_curr_token.tk_name != 0)
811 	       ? _nc_curr_token.tk_name
812 	       : "<null>"),
813 	      type));
814 
815     DEBUG(3, (T_RETURN("%d"), type));
816     return (type);
817 }
818 
819 /*
820  *	char
821  *	trans_string(ptr)
822  *
823  *	Reads characters using next_char() until encountering a separator, nl,
824  *	or end-of-file.  The returned value is the character which caused
825  *	reading to stop.  The following translations are done on the input:
826  *
827  *		^X  goes to  ctrl-X (i.e. X & 037)
828  *		{\E,\n,\r,\b,\t,\f}  go to
829  *			{ESCAPE,newline,carriage-return,backspace,tab,formfeed}
830  *		{\^,\\}  go to  {carat,backslash}
831  *		\ddd (for ddd = up to three octal digits)  goes to the character ddd
832  *
833  *		\e == \E
834  *		\0 == \200
835  *
836  */
837 
838 NCURSES_EXPORT(int)
839 _nc_trans_string(char *ptr, const char *const last)
840 {
841     int count = 0;
842     int number = 0;
843     int i, c;
844     int last_ch = '\0';
845     bool ignored = FALSE;
846     bool long_warning = FALSE;
847 
848     while ((c = next_char()) != separator && c != EOF) {
849 	if (ptr >= (last - 1)) {
850 	    if (c != EOF) {
851 		while ((c = next_char()) != separator && c != EOF) {
852 		    ;
853 		}
854 	    }
855 	    break;
856 	}
857 	if ((_nc_syntax == SYN_TERMCAP) && c == '\n')
858 	    break;
859 	if (c == '^' && last_ch != '%') {
860 	    c = next_char();
861 	    if (c == EOF)
862 		_nc_err_abort(MSG_NO_INPUTS);
863 
864 	    if (!(is7bits(c) && isprint(c))) {
865 		_nc_warning("Illegal ^ character - '%s'", unctrl(UChar(c)));
866 	    }
867 	    if (c == '?' && (_nc_syntax != SYN_TERMCAP)) {
868 		*(ptr++) = '\177';
869 	    } else {
870 		if ((c &= 037) == 0)
871 		    c = 128;
872 		*(ptr++) = (char) (c);
873 	    }
874 	} else if (c == '\\') {
875 	    bool strict_bsd = ((_nc_syntax == SYN_TERMCAP) && _nc_strict_bsd);
876 
877 	    c = next_char();
878 	    if (c == EOF)
879 		_nc_err_abort(MSG_NO_INPUTS);
880 
881 	    if (isoctal(c) || (strict_bsd && isdigit(c))) {
882 		number = c - '0';
883 		for (i = 0; i < 2; i++) {
884 		    c = next_char();
885 		    if (c == EOF)
886 			_nc_err_abort(MSG_NO_INPUTS);
887 
888 		    if (!isoctal(c)) {
889 			if (isdigit(c)) {
890 			    if (!strict_bsd) {
891 				_nc_warning("Non-octal digit `%c' in \\ sequence", c);
892 				/* allow the digit; it'll do less harm */
893 			    }
894 			} else {
895 			    push_back(c);
896 			    break;
897 			}
898 		    }
899 
900 		    number = number * 8 + c - '0';
901 		}
902 
903 		number = UChar(number);
904 		if (number == 0 && !strict_bsd)
905 		    number = 0200;
906 		*(ptr++) = (char) number;
907 	    } else {
908 		switch (c) {
909 		case 'E':
910 		    *(ptr++) = '\033';
911 		    break;
912 
913 		case 'n':
914 		    *(ptr++) = '\n';
915 		    break;
916 
917 		case 'r':
918 		    *(ptr++) = '\r';
919 		    break;
920 
921 		case 'b':
922 		    *(ptr++) = '\010';
923 		    break;
924 
925 		case 'f':
926 		    *(ptr++) = '\014';
927 		    break;
928 
929 		case 't':
930 		    *(ptr++) = '\t';
931 		    break;
932 
933 		case '\\':
934 		    *(ptr++) = '\\';
935 		    break;
936 
937 		case '^':
938 		    *(ptr++) = '^';
939 		    break;
940 
941 		case ',':
942 		    *(ptr++) = ',';
943 		    break;
944 
945 		case '\n':
946 		    continue;
947 
948 		default:
949 		    if ((_nc_syntax == SYN_TERMINFO) || !_nc_strict_bsd) {
950 			switch (c) {
951 			case 'a':
952 			    c = '\007';
953 			    break;
954 			case 'e':
955 			    c = '\033';
956 			    break;
957 			case 'l':
958 			    c = '\n';
959 			    break;
960 			case 's':
961 			    c = ' ';
962 			    break;
963 			case ':':
964 			    c = ':';
965 			    break;
966 			default:
967 			    _nc_warning("Illegal character '%s' in \\ sequence",
968 					unctrl(UChar(c)));
969 			    break;
970 			}
971 		    }
972 		    /* FALLTHRU */
973 		case '|':
974 		    *(ptr++) = (char) c;
975 		}		/* endswitch (c) */
976 	    }			/* endelse (c < '0' ||  c > '7') */
977 	}
978 	/* end else if (c == '\\') */
979 	else if (c == '\n' && (_nc_syntax == SYN_TERMINFO)) {
980 	    /*
981 	     * Newlines embedded in a terminfo string are ignored, provided
982 	     * that the next line begins with whitespace.
983 	     */
984 	    ignored = TRUE;
985 	} else {
986 	    *(ptr++) = (char) c;
987 	}
988 
989 	if (!ignored) {
990 	    if (_nc_curr_col <= 1) {
991 		push_back(c);
992 		c = '\n';
993 		break;
994 	    }
995 	    last_ch = c;
996 	    count++;
997 	}
998 	ignored = FALSE;
999 
1000 	if (count > MAXCAPLEN && !long_warning) {
1001 	    _nc_warning("Very long string found.  Missing separator?");
1002 	    long_warning = TRUE;
1003 	}
1004     }				/* end while */
1005 
1006     *ptr = '\0';
1007 
1008     return (c);
1009 }
1010 
1011 /*
1012  *	_nc_push_token()
1013  *
1014  *	Push a token of given type so that it will be reread by the next
1015  *	get_token() call.
1016  */
1017 
1018 NCURSES_EXPORT(void)
1019 _nc_push_token(int tokclass)
1020 {
1021     /*
1022      * This implementation is kind of bogus, it will fail if we ever do more
1023      * than one pushback at a time between get_token() calls.  It relies on the
1024      * fact that _nc_curr_token is static storage that nothing but
1025      * _nc_get_token() touches.
1026      */
1027     pushtype = tokclass;
1028     if (pushname == 0)
1029 	pushname = typeMalloc(char, MAX_NAME_SIZE + 1);
1030     _nc_get_type(pushname);
1031 
1032     DEBUG(3, ("pushing token: `%s', class %d",
1033 	      ((_nc_curr_token.tk_name != 0)
1034 	       ? _nc_curr_token.tk_name
1035 	       : "<null>"),
1036 	      pushtype));
1037 }
1038 
1039 /*
1040  * Panic mode error recovery - skip everything until a "ch" is found.
1041  */
1042 NCURSES_EXPORT(void)
1043 _nc_panic_mode(char ch)
1044 {
1045     for (;;) {
1046 	int c = next_char();
1047 	if (c == ch)
1048 	    return;
1049 	if (c == EOF)
1050 	    return;
1051     }
1052 }
1053 
1054 #if NO_LEAKS
1055 NCURSES_EXPORT(void)
1056 _nc_comp_scan_leaks(void)
1057 {
1058     if (pushname != 0) {
1059 	FreeAndNull(pushname);
1060     }
1061     if (tok_buf != 0) {
1062 	FreeAndNull(tok_buf);
1063     }
1064 }
1065 #endif
1066