1/****************************************************************************
2 * Copyright (c) 1998-2006,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_scan.c --- Lexical scanner for terminfo compiler.
37 *
38 *	_nc_reset_input()
39 *	_nc_get_token()
40 *	_nc_panic_mode()
41 *	int _nc_syntax;
42 *	int _nc_curr_line;
43 *	long _nc_curr_file_pos;
44 *	long _nc_comment_start;
45 *	long _nc_comment_end;
46 */
47
48#include <curses.priv.h>
49
50#include <ctype.h>
51#include <term_entry.h>
52#include <tic.h>
53
54MODULE_ID("$Id: comp_scan.c,v 1.83 2008/08/16 19:22:55 tom Exp $")
55
56/*
57 * Maximum length of string capability we'll accept before raising an error.
58 * Yes, there is a real capability in /etc/termcap this long, an "is".
59 */
60#define MAXCAPLEN	600
61
62#define iswhite(ch)	(ch == ' '  ||  ch == '\t')
63
64NCURSES_EXPORT_VAR(int)
65_nc_syntax = 0;			/* termcap or terminfo? */
66NCURSES_EXPORT_VAR(long)
67_nc_curr_file_pos = 0;		/* file offset of current line */
68NCURSES_EXPORT_VAR(long)
69_nc_comment_start = 0;		/* start of comment range before name */
70NCURSES_EXPORT_VAR(long)
71_nc_comment_end = 0;		/* end of comment range before name */
72NCURSES_EXPORT_VAR(long)
73_nc_start_line = 0;		/* start line of current entry */
74
75NCURSES_EXPORT_VAR(struct token)
76_nc_curr_token =
77{
78    0, 0, 0
79};
80
81/*****************************************************************************
82 *
83 * Token-grabbing machinery
84 *
85 *****************************************************************************/
86
87static bool first_column;	/* See 'next_char()' below */
88static bool had_newline;
89static char separator;		/* capability separator */
90static int pushtype;		/* type of pushback token */
91static char *pushname;
92
93#if NCURSES_EXT_FUNCS
94NCURSES_EXPORT_VAR(bool)
95_nc_disable_period = FALSE;	/* used by tic -a option */
96#endif
97
98/*****************************************************************************
99 *
100 * Character-stream handling
101 *
102 *****************************************************************************/
103
104#define LEXBUFSIZ	1024
105
106static char *bufptr;		/* otherwise, the input buffer pointer */
107static char *bufstart;		/* start of buffer so we can compute offsets */
108static FILE *yyin;		/* scanner's input file descriptor */
109
110/*
111 *	_nc_reset_input()
112 *
113 *	Resets the input-reading routines.  Used on initialization,
114 *	or after a seek has been done.  Exactly one argument must be
115 *	non-null.
116 */
117
118NCURSES_EXPORT(void)
119_nc_reset_input(FILE *fp, char *buf)
120{
121    pushtype = NO_PUSHBACK;
122    if (pushname != 0)
123	pushname[0] = '\0';
124    yyin = fp;
125    bufstart = bufptr = buf;
126    _nc_curr_file_pos = 0L;
127    if (fp != 0)
128	_nc_curr_line = 0;
129    _nc_curr_col = 0;
130}
131
132/*
133 *	int last_char()
134 *
135 *	Returns the final nonblank character on the current input buffer
136 */
137static int
138last_char(void)
139{
140    size_t len = strlen(bufptr);
141    while (len--) {
142	if (!isspace(UChar(bufptr[len])))
143	    return bufptr[len];
144    }
145    return 0;
146}
147
148/*
149 *	int next_char()
150 *
151 *	Returns the next character in the input stream.  Comments and leading
152 *	white space are stripped.
153 *
154 *	The global state variable 'firstcolumn' is set TRUE if the character
155 *	returned is from the first column of the input line.
156 *
157 *	The global variable _nc_curr_line is incremented for each new line.
158 *	The global variable _nc_curr_file_pos is set to the file offset of the
159 *	beginning of each line.
160 */
161
162static int
163next_char(void)
164{
165    static char *result;
166    static size_t allocated;
167    int the_char;
168
169    if (!yyin) {
170	if (result != 0) {
171	    FreeAndNull(result);
172	    FreeAndNull(pushname);
173	    allocated = 0;
174	}
175	/*
176	 * An string with an embedded null will truncate the input.  This is
177	 * intentional (we don't read binary files here).
178	 */
179	if (bufptr == 0 || *bufptr == '\0')
180	    return (EOF);
181	if (*bufptr == '\n') {
182	    _nc_curr_line++;
183	    _nc_curr_col = 0;
184	} else if (*bufptr == '\t') {
185	    _nc_curr_col = (_nc_curr_col | 7);
186	}
187    } else if (!bufptr || !*bufptr) {
188	/*
189	 * In theory this could be recoded to do its I/O one character at a
190	 * time, saving the buffer space.  In practice, this turns out to be
191	 * quite hard to get completely right.  Try it and see.  If you
192	 * succeed, don't forget to hack push_back() correspondingly.
193	 */
194	size_t used;
195	size_t len;
196
197	do {
198	    bufstart = 0;
199	    used = 0;
200	    do {
201		if (used + (LEXBUFSIZ / 4) >= allocated) {
202		    allocated += (allocated + LEXBUFSIZ);
203		    result = typeRealloc(char, allocated, result);
204		    if (result == 0)
205			return (EOF);
206		    bufstart = result;
207		}
208		if (used == 0)
209		    _nc_curr_file_pos = ftell(yyin);
210
211		if (fgets(result + used, (int) (allocated - used), yyin) != 0) {
212		    bufstart = result;
213		    if (used == 0) {
214			_nc_curr_line++;
215			_nc_curr_col = 0;
216		    }
217		} else {
218		    if (used != 0)
219			strcat(result, "\n");
220		}
221		if ((bufptr = bufstart) != 0) {
222		    used = strlen(bufptr);
223		    while (iswhite(*bufptr)) {
224			if (*bufptr == '\t') {
225			    _nc_curr_col = (_nc_curr_col | 7) + 1;
226			} else {
227			    _nc_curr_col++;
228			}
229			bufptr++;
230		    }
231
232		    /*
233		     * Treat a trailing <cr><lf> the same as a <newline> so we
234		     * can read files on OS/2, etc.
235		     */
236		    if ((len = strlen(bufptr)) > 1) {
237			if (bufptr[len - 1] == '\n'
238			    && bufptr[len - 2] == '\r') {
239			    len--;
240			    bufptr[len - 1] = '\n';
241			    bufptr[len] = '\0';
242			}
243		    }
244		} else {
245		    return (EOF);
246		}
247	    } while (bufptr[len - 1] != '\n');	/* complete a line */
248	} while (result[0] == '#');	/* ignore comments */
249    } else if (*bufptr == '\t') {
250	_nc_curr_col = (_nc_curr_col | 7);
251    }
252
253    first_column = (bufptr == bufstart);
254    if (first_column)
255	had_newline = FALSE;
256
257    _nc_curr_col++;
258    the_char = *bufptr++;
259    return UChar(the_char);
260}
261
262static void
263push_back(char c)
264/* push a character back onto the input stream */
265{
266    if (bufptr == bufstart)
267	_nc_syserr_abort("Can't backspace off beginning of line");
268    *--bufptr = c;
269    _nc_curr_col--;
270}
271
272static long
273stream_pos(void)
274/* return our current character position in the input stream */
275{
276    return (yyin ? ftell(yyin) : (bufptr ? bufptr - bufstart : 0));
277}
278
279static bool
280end_of_stream(void)
281/* are we at end of input? */
282{
283    return ((yyin ? feof(yyin) : (bufptr && *bufptr == '\0'))
284	    ? TRUE : FALSE);
285}
286
287/* Assume we may be looking at a termcap-style continuation */
288static NCURSES_INLINE int
289eat_escaped_newline(int ch)
290{
291    if (ch == '\\')
292	while ((ch = next_char()) == '\n' || iswhite(ch))
293	    continue;
294    return ch;
295}
296
297#define TOK_BUF_SIZE MAX_ENTRY_SIZE
298
299#define OkToAdd() \
300	((tok_ptr - tok_buf) < (TOK_BUF_SIZE - 2))
301
302#define AddCh(ch) \
303	*tok_ptr++ = (char) ch; \
304	*tok_ptr = '\0'
305
306/*
307 *	int
308 *	get_token()
309 *
310 *	Scans the input for the next token, storing the specifics in the
311 *	global structure 'curr_token' and returning one of the following:
312 *
313 *		NAMES		A line beginning in column 1.  'name'
314 *				will be set to point to everything up to but
315 *				not including the first separator on the line.
316 *		BOOLEAN		An entry consisting of a name followed by
317 *				a separator.  'name' will be set to point to
318 *				the name of the capability.
319 *		NUMBER		An entry of the form
320 *					name#digits,
321 *				'name' will be set to point to the capability
322 *				name and 'valnumber' to the number given.
323 *		STRING		An entry of the form
324 *					name=characters,
325 *				'name' is set to the capability name and
326 *				'valstring' to the string of characters, with
327 *				input translations done.
328 *		CANCEL		An entry of the form
329 *					name@,
330 *				'name' is set to the capability name and
331 *				'valnumber' to -1.
332 *		EOF		The end of the file has been reached.
333 *
334 *	A `separator' is either a comma or a semicolon, depending on whether
335 *	we are in termcap or terminfo mode.
336 *
337 */
338
339NCURSES_EXPORT(int)
340_nc_get_token(bool silent)
341{
342    static const char terminfo_punct[] = "@%&*!#";
343    static char *tok_buf;
344
345    char *after_list;
346    char *after_name;
347    char *numchk;
348    char *tok_ptr;
349    char *s;
350    char numbuf[80];
351    int ch;
352    int dot_flag = FALSE;
353    int type;
354    long number;
355    long token_start;
356    unsigned found;
357#ifdef TRACE
358    int old_line;
359    int old_col;
360#endif
361
362    if (pushtype != NO_PUSHBACK) {
363	int retval = pushtype;
364
365	_nc_set_type(pushname != 0 ? pushname : "");
366	DEBUG(3, ("pushed-back token: `%s', class %d",
367		  _nc_curr_token.tk_name, pushtype));
368
369	pushtype = NO_PUSHBACK;
370	if (pushname != 0)
371	    pushname[0] = '\0';
372
373	/* currtok wasn't altered by _nc_push_token() */
374	return (retval);
375    }
376
377    if (end_of_stream()) {
378	yyin = 0;
379	next_char();		/* frees its allocated memory */
380	if (tok_buf != 0) {
381	    if (_nc_curr_token.tk_name == tok_buf)
382		_nc_curr_token.tk_name = 0;
383	    FreeAndNull(tok_buf);
384	}
385	return (EOF);
386    }
387
388  start_token:
389    token_start = stream_pos();
390    while ((ch = next_char()) == '\n' || iswhite(ch)) {
391	if (ch == '\n')
392	    had_newline = TRUE;
393	continue;
394    }
395
396    ch = eat_escaped_newline(ch);
397
398#ifdef TRACE
399    old_line = _nc_curr_line;
400    old_col = _nc_curr_col;
401#endif
402    if (ch == EOF)
403	type = EOF;
404    else {
405	/* if this is a termcap entry, skip a leading separator */
406	if (separator == ':' && ch == ':')
407	    ch = next_char();
408
409	if (ch == '.'
410#if NCURSES_EXT_FUNCS
411	    && !_nc_disable_period
412#endif
413	    ) {
414	    dot_flag = TRUE;
415	    DEBUG(8, ("dot-flag set"));
416
417	    while ((ch = next_char()) == '.' || iswhite(ch))
418		continue;
419	}
420
421	if (ch == EOF) {
422	    type = EOF;
423	    goto end_of_token;
424	}
425
426	/* have to make some punctuation chars legal for terminfo */
427	if (!isalnum(UChar(ch))
428#if NCURSES_EXT_FUNCS
429	    && !(ch == '.' && _nc_disable_period)
430#endif
431	    && !strchr(terminfo_punct, (char) ch)) {
432	    if (!silent)
433		_nc_warning("Illegal character (expected alphanumeric or %s) - '%s'",
434			    terminfo_punct, unctrl((chtype) ch));
435	    _nc_panic_mode(separator);
436	    goto start_token;
437	}
438
439	if (tok_buf == 0)
440	    tok_buf = typeMalloc(char, TOK_BUF_SIZE);
441
442#ifdef TRACE
443	old_line = _nc_curr_line;
444	old_col = _nc_curr_col;
445#endif
446	tok_ptr = tok_buf;
447	AddCh(ch);
448
449	if (first_column) {
450	    _nc_comment_start = token_start;
451	    _nc_comment_end = _nc_curr_file_pos;
452	    _nc_start_line = _nc_curr_line;
453
454	    _nc_syntax = ERR;
455	    after_name = 0;
456	    after_list = 0;
457	    while ((ch = next_char()) != '\n') {
458		if (ch == EOF) {
459		    _nc_err_abort(MSG_NO_INPUTS);
460		} else if (ch == '|') {
461		    after_list = tok_ptr;
462		    if (after_name == 0)
463			after_name = tok_ptr;
464		} else if (ch == ':' && last_char() != ',') {
465		    _nc_syntax = SYN_TERMCAP;
466		    separator = ':';
467		    break;
468		} else if (ch == ',') {
469		    _nc_syntax = SYN_TERMINFO;
470		    separator = ',';
471		    /*
472		     * If we did not see a '|', then we found a name with no
473		     * aliases or description.
474		     */
475		    if (after_name == 0)
476			break;
477		    /*
478		     * If we see a comma, we assume this is terminfo unless we
479		     * subsequently run into a colon.  But we don't stop
480		     * looking for a colon until hitting a newline.  This
481		     * allows commas to be embedded in description fields of
482		     * either syntax.
483		     */
484		} else
485		    ch = eat_escaped_newline(ch);
486
487		if (OkToAdd()) {
488		    AddCh(ch);
489		} else {
490		    ch = EOF;
491		    break;
492		}
493	    }
494	    *tok_ptr = '\0';
495	    if (_nc_syntax == ERR) {
496		/*
497		 * Grrr...what we ought to do here is barf, complaining that
498		 * the entry is malformed.  But because a couple of name fields
499		 * in the 8.2 termcap file end with |\, we just have to assume
500		 * it's termcap syntax.
501		 */
502		_nc_syntax = SYN_TERMCAP;
503		separator = ':';
504	    } else if (_nc_syntax == SYN_TERMINFO) {
505		/* throw away trailing /, *$/ */
506		for (--tok_ptr;
507		     iswhite(*tok_ptr) || *tok_ptr == ',';
508		     tok_ptr--)
509		    continue;
510		tok_ptr[1] = '\0';
511	    }
512
513	    /*
514	     * This is the soonest we have the terminal name fetched.  Set up
515	     * for following warning messages.  If there's no '|', then there
516	     * is no description.
517	     */
518	    if (after_name != 0) {
519		ch = *after_name;
520		*after_name = '\0';
521		_nc_set_type(tok_buf);
522		*after_name = (char) ch;
523	    }
524
525	    /*
526	     * Compute the boundary between the aliases and the description
527	     * field for syntax-checking purposes.
528	     */
529	    if (after_list != 0) {
530		if (!silent) {
531		    if (*after_list == '\0')
532			_nc_warning("empty longname field");
533		    else if (strchr(after_list, ' ') == 0)
534			_nc_warning("older tic versions may treat the description field as an alias");
535		}
536	    } else {
537		after_list = tok_buf + strlen(tok_buf);
538		DEBUG(1, ("missing description"));
539	    }
540
541	    /*
542	     * Whitespace in a name field other than the long name can confuse
543	     * rdist and some termcap tools.  Slashes are a no-no.  Other
544	     * special characters can be dangerous due to shell expansion.
545	     */
546	    for (s = tok_buf; s < after_list; ++s) {
547		if (isspace(UChar(*s))) {
548		    if (!silent)
549			_nc_warning("whitespace in name or alias field");
550		    break;
551		} else if (*s == '/') {
552		    if (!silent)
553			_nc_warning("slashes aren't allowed in names or aliases");
554		    break;
555		} else if (strchr("$[]!*?", *s)) {
556		    if (!silent)
557			_nc_warning("dubious character `%c' in name or alias field", *s);
558		    break;
559		}
560	    }
561
562	    _nc_curr_token.tk_name = tok_buf;
563	    type = NAMES;
564	} else {
565	    if (had_newline && _nc_syntax == SYN_TERMCAP) {
566		_nc_warning("Missing backslash before newline");
567		had_newline = FALSE;
568	    }
569	    while ((ch = next_char()) != EOF) {
570		if (!isalnum(UChar(ch))) {
571		    if (_nc_syntax == SYN_TERMINFO) {
572			if (ch != '_')
573			    break;
574		    } else {	/* allow ';' for "k;" */
575			if (ch != ';')
576			    break;
577		    }
578		}
579		if (OkToAdd()) {
580		    AddCh(ch);
581		} else {
582		    ch = EOF;
583		    break;
584		}
585	    }
586
587	    *tok_ptr++ = '\0';	/* separate name/value in buffer */
588	    switch (ch) {
589	    case ',':
590	    case ':':
591		if (ch != separator)
592		    _nc_err_abort("Separator inconsistent with syntax");
593		_nc_curr_token.tk_name = tok_buf;
594		type = BOOLEAN;
595		break;
596	    case '@':
597		if ((ch = next_char()) != separator && !silent)
598		    _nc_warning("Missing separator after `%s', have %s",
599				tok_buf, unctrl((chtype) ch));
600		_nc_curr_token.tk_name = tok_buf;
601		type = CANCEL;
602		break;
603
604	    case '#':
605		found = 0;
606		while (isalnum(ch = next_char())) {
607		    numbuf[found++] = (char) ch;
608		    if (found >= sizeof(numbuf) - 1)
609			break;
610		}
611		numbuf[found] = '\0';
612		number = strtol(numbuf, &numchk, 0);
613		if (!silent) {
614		    if (numchk == numbuf)
615			_nc_warning("no value given for `%s'", tok_buf);
616		    if ((*numchk != '\0') || (ch != separator))
617			_nc_warning("Missing separator");
618		}
619		_nc_curr_token.tk_name = tok_buf;
620		_nc_curr_token.tk_valnumber = number;
621		type = NUMBER;
622		break;
623
624	    case '=':
625		ch = _nc_trans_string(tok_ptr, tok_buf + TOK_BUF_SIZE);
626		if (!silent && ch != separator)
627		    _nc_warning("Missing separator");
628		_nc_curr_token.tk_name = tok_buf;
629		_nc_curr_token.tk_valstring = tok_ptr;
630		type = STRING;
631		break;
632
633	    case EOF:
634		type = EOF;
635		break;
636	    default:
637		/* just to get rid of the compiler warning */
638		type = UNDEF;
639		if (!silent)
640		    _nc_warning("Illegal character - '%s'", unctrl((chtype) ch));
641	    }
642	}			/* end else (first_column == FALSE) */
643    }				/* end else (ch != EOF) */
644
645  end_of_token:
646
647#ifdef TRACE
648    if (dot_flag == TRUE)
649	DEBUG(8, ("Commented out "));
650
651    if (_nc_tracing >= DEBUG_LEVEL(8)) {
652	_tracef("parsed %d.%d to %d.%d",
653		old_line, old_col,
654		_nc_curr_line, _nc_curr_col);
655    }
656    if (_nc_tracing >= DEBUG_LEVEL(7)) {
657	switch (type) {
658	case BOOLEAN:
659	    _tracef("Token: Boolean; name='%s'",
660		    _nc_curr_token.tk_name);
661	    break;
662
663	case NUMBER:
664	    _tracef("Token: Number;  name='%s', value=%d",
665		    _nc_curr_token.tk_name,
666		    _nc_curr_token.tk_valnumber);
667	    break;
668
669	case STRING:
670	    _tracef("Token: String;  name='%s', value=%s",
671		    _nc_curr_token.tk_name,
672		    _nc_visbuf(_nc_curr_token.tk_valstring));
673	    break;
674
675	case CANCEL:
676	    _tracef("Token: Cancel; name='%s'",
677		    _nc_curr_token.tk_name);
678	    break;
679
680	case NAMES:
681
682	    _tracef("Token: Names; value='%s'",
683		    _nc_curr_token.tk_name);
684	    break;
685
686	case EOF:
687	    _tracef("Token: End of file");
688	    break;
689
690	default:
691	    _nc_warning("Bad token type");
692	}
693    }
694#endif
695
696    if (dot_flag == TRUE)	/* if commented out, use the next one */
697	type = _nc_get_token(silent);
698
699    DEBUG(3, ("token: `%s', class %d",
700	      ((_nc_curr_token.tk_name != 0)
701	       ? _nc_curr_token.tk_name
702	       : "<null>"),
703	      type));
704
705    return (type);
706}
707
708/*
709 *	char
710 *	trans_string(ptr)
711 *
712 *	Reads characters using next_char() until encountering a separator, nl,
713 *	or end-of-file.  The returned value is the character which caused
714 *	reading to stop.  The following translations are done on the input:
715 *
716 *		^X  goes to  ctrl-X (i.e. X & 037)
717 *		{\E,\n,\r,\b,\t,\f}  go to
718 *			{ESCAPE,newline,carriage-return,backspace,tab,formfeed}
719 *		{\^,\\}  go to  {carat,backslash}
720 *		\ddd (for ddd = up to three octal digits)  goes to the character ddd
721 *
722 *		\e == \E
723 *		\0 == \200
724 *
725 */
726
727NCURSES_EXPORT(int)
728_nc_trans_string(char *ptr, char *last)
729{
730    int count = 0;
731    int number = 0;
732    int i, c;
733    chtype ch, last_ch = '\0';
734    bool ignored = FALSE;
735    bool long_warning = FALSE;
736
737    while ((ch = c = next_char()) != (chtype) separator && c != EOF) {
738	if (ptr >= (last - 1)) {
739	    if (c != EOF) {
740		while ((c = next_char()) != separator && c != EOF) {
741		    ;
742		}
743		ch = c;
744	    }
745	    break;
746	}
747	if ((_nc_syntax == SYN_TERMCAP) && c == '\n')
748	    break;
749	if (ch == '^' && last_ch != '%') {
750	    ch = c = next_char();
751	    if (c == EOF)
752		_nc_err_abort(MSG_NO_INPUTS);
753
754	    if (!(is7bits(ch) && isprint(ch))) {
755		_nc_warning("Illegal ^ character - '%s'", unctrl(ch));
756	    }
757	    if (ch == '?') {
758		*(ptr++) = '\177';
759		if (_nc_tracing)
760		    _nc_warning("Allow ^? as synonym for \\177");
761	    } else {
762		if ((ch &= 037) == 0)
763		    ch = 128;
764		*(ptr++) = (char) (ch);
765	    }
766	} else if (ch == '\\') {
767	    ch = c = next_char();
768	    if (c == EOF)
769		_nc_err_abort(MSG_NO_INPUTS);
770
771	    if (ch >= '0' && ch <= '7') {
772		number = ch - '0';
773		for (i = 0; i < 2; i++) {
774		    ch = c = next_char();
775		    if (c == EOF)
776			_nc_err_abort(MSG_NO_INPUTS);
777
778		    if (c < '0' || c > '7') {
779			if (isdigit(c)) {
780			    _nc_warning("Non-octal digit `%c' in \\ sequence", c);
781			    /* allow the digit; it'll do less harm */
782			} else {
783			    push_back((char) c);
784			    break;
785			}
786		    }
787
788		    number = number * 8 + c - '0';
789		}
790
791		if (number == 0)
792		    number = 0200;
793		*(ptr++) = (char) number;
794	    } else {
795		switch (c) {
796		case 'E':
797		case 'e':
798		    *(ptr++) = '\033';
799		    break;
800
801		case 'a':
802		    *(ptr++) = '\007';
803		    break;
804
805		case 'l':
806		case 'n':
807		    *(ptr++) = '\n';
808		    break;
809
810		case 'r':
811		    *(ptr++) = '\r';
812		    break;
813
814		case 'b':
815		    *(ptr++) = '\010';
816		    break;
817
818		case 's':
819		    *(ptr++) = ' ';
820		    break;
821
822		case 'f':
823		    *(ptr++) = '\014';
824		    break;
825
826		case 't':
827		    *(ptr++) = '\t';
828		    break;
829
830		case '\\':
831		    *(ptr++) = '\\';
832		    break;
833
834		case '^':
835		    *(ptr++) = '^';
836		    break;
837
838		case ',':
839		    *(ptr++) = ',';
840		    break;
841
842		case ':':
843		    *(ptr++) = ':';
844		    break;
845
846		case '\n':
847		    continue;
848
849		default:
850		    _nc_warning("Illegal character '%s' in \\ sequence",
851				unctrl(ch));
852		    /* FALLTHRU */
853		case '|':
854		    *(ptr++) = (char) ch;
855		}		/* endswitch (ch) */
856	    }			/* endelse (ch < '0' ||  ch > '7') */
857	}
858	/* end else if (ch == '\\') */
859	else if (ch == '\n' && (_nc_syntax == SYN_TERMINFO)) {
860	    /*
861	     * Newlines embedded in a terminfo string are ignored, provided
862	     * that the next line begins with whitespace.
863	     */
864	    ignored = TRUE;
865	} else {
866	    *(ptr++) = (char) ch;
867	}
868
869	if (!ignored) {
870	    if (_nc_curr_col <= 1) {
871		push_back((char) ch);
872		ch = '\n';
873		break;
874	    }
875	    last_ch = ch;
876	    count++;
877	}
878	ignored = FALSE;
879
880	if (count > MAXCAPLEN && !long_warning) {
881	    _nc_warning("Very long string found.  Missing separator?");
882	    long_warning = TRUE;
883	}
884    }				/* end while */
885
886    *ptr = '\0';
887
888    return (ch);
889}
890
891/*
892 *	_nc_push_token()
893 *
894 *	Push a token of given type so that it will be reread by the next
895 *	get_token() call.
896 */
897
898NCURSES_EXPORT(void)
899_nc_push_token(int tokclass)
900{
901    /*
902     * This implementation is kind of bogus, it will fail if we ever do more
903     * than one pushback at a time between get_token() calls.  It relies on the
904     * fact that _nc_curr_token is static storage that nothing but
905     * _nc_get_token() touches.
906     */
907    pushtype = tokclass;
908    if (pushname == 0)
909	pushname = typeMalloc(char, MAX_NAME_SIZE + 1);
910    _nc_get_type(pushname);
911
912    DEBUG(3, ("pushing token: `%s', class %d",
913	      ((_nc_curr_token.tk_name != 0)
914	       ? _nc_curr_token.tk_name
915	       : "<null>"),
916	      pushtype));
917}
918
919/*
920 * Panic mode error recovery - skip everything until a "ch" is found.
921 */
922NCURSES_EXPORT(void)
923_nc_panic_mode(char ch)
924{
925    int c;
926
927    for (;;) {
928	c = next_char();
929	if (c == ch)
930	    return;
931	if (c == EOF)
932	    return;
933    }
934}
935
936#if NO_LEAKS
937NCURSES_EXPORT(void)
938_nc_comp_scan_leaks(void)
939{
940    if (pushname != 0) {
941	FreeAndNull(pushname);
942    }
943}
944#endif
945