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