1/****************************************************************************
2 * Copyright (c) 1998-2004,2005 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.76 2005/06/04 22:04:45 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		}
207		if (used == 0)
208		    _nc_curr_file_pos = ftell(yyin);
209
210		if (fgets(result + used, (int) (allocated - used), yyin) != 0) {
211		    bufstart = result;
212		    if (used == 0) {
213			_nc_curr_line++;
214			_nc_curr_col = 0;
215		    }
216		} else {
217		    if (used != 0)
218			strcat(result, "\n");
219		}
220		if ((bufptr = bufstart) != 0) {
221		    used = strlen(bufptr);
222		    while (iswhite(*bufptr)) {
223			if (*bufptr == '\t') {
224			    _nc_curr_col = (_nc_curr_col | 7) + 1;
225			} else {
226			    _nc_curr_col++;
227			}
228			bufptr++;
229		    }
230
231		    /*
232		     * Treat a trailing <cr><lf> the same as a <newline> so we
233		     * can read files on OS/2, etc.
234		     */
235		    if ((len = strlen(bufptr)) > 1) {
236			if (bufptr[len - 1] == '\n'
237			    && bufptr[len - 2] == '\r') {
238			    len--;
239			    bufptr[len - 1] = '\n';
240			    bufptr[len] = '\0';
241			}
242		    }
243		} else {
244		    return (EOF);
245		}
246	    } while (bufptr[len - 1] != '\n');	/* complete a line */
247	} while (result[0] == '#');	/* ignore comments */
248    } else if (*bufptr == '\t') {
249	_nc_curr_col = (_nc_curr_col | 7);
250    }
251
252    first_column = (bufptr == bufstart);
253    if (first_column)
254	had_newline = FALSE;
255
256    _nc_curr_col++;
257    the_char = *bufptr++;
258    return UChar(the_char);
259}
260
261static void
262push_back(char c)
263/* push a character back onto the input stream */
264{
265    if (bufptr == bufstart)
266	_nc_syserr_abort("Can't backspace off beginning of line");
267    *--bufptr = c;
268    _nc_curr_col--;
269}
270
271static long
272stream_pos(void)
273/* return our current character position in the input stream */
274{
275    return (yyin ? ftell(yyin) : (bufptr ? bufptr - bufstart : 0));
276}
277
278static bool
279end_of_stream(void)
280/* are we at end of input? */
281{
282    return ((yyin ? feof(yyin) : (bufptr && *bufptr == '\0'))
283	    ? TRUE : FALSE);
284}
285
286/* Assume we may be looking at a termcap-style continuation */
287static inline int
288eat_escaped_newline(int ch)
289{
290    if (ch == '\\')
291	while ((ch = next_char()) == '\n' || iswhite(ch))
292	    continue;
293    return ch;
294}
295
296/*
297 *	int
298 *	get_token()
299 *
300 *	Scans the input for the next token, storing the specifics in the
301 *	global structure 'curr_token' and returning one of the following:
302 *
303 *		NAMES		A line beginning in column 1.  'name'
304 *				will be set to point to everything up to but
305 *				not including the first separator on the line.
306 *		BOOLEAN		An entry consisting of a name followed by
307 *				a separator.  'name' will be set to point to
308 *				the name of the capability.
309 *		NUMBER		An entry of the form
310 *					name#digits,
311 *				'name' will be set to point to the capability
312 *				name and 'valnumber' to the number given.
313 *		STRING		An entry of the form
314 *					name=characters,
315 *				'name' is set to the capability name and
316 *				'valstring' to the string of characters, with
317 *				input translations done.
318 *		CANCEL		An entry of the form
319 *					name@,
320 *				'name' is set to the capability name and
321 *				'valnumber' to -1.
322 *		EOF		The end of the file has been reached.
323 *
324 *	A `separator' is either a comma or a semicolon, depending on whether
325 *	we are in termcap or terminfo mode.
326 *
327 */
328
329NCURSES_EXPORT(int)
330_nc_get_token(bool silent)
331{
332    static const char terminfo_punct[] = "@%&*!#";
333    static char *buffer;
334
335    char *after_list;
336    char *after_name;
337    char *numchk;
338    char *ptr;
339    char *s;
340    char numbuf[80];
341    int ch;
342    int dot_flag = FALSE;
343    int type;
344    long number;
345    long token_start;
346    unsigned found;
347#ifdef TRACE
348    int old_line;
349    int old_col;
350#endif
351
352    if (pushtype != NO_PUSHBACK) {
353	int retval = pushtype;
354
355	_nc_set_type(pushname != 0 ? pushname : "");
356	DEBUG(3, ("pushed-back token: `%s', class %d",
357		  _nc_curr_token.tk_name, pushtype));
358
359	pushtype = NO_PUSHBACK;
360	if (pushname != 0)
361	    pushname[0] = '\0';
362
363	/* currtok wasn't altered by _nc_push_token() */
364	return (retval);
365    }
366
367    if (end_of_stream()) {
368	yyin = 0;
369	next_char();		/* frees its allocated memory */
370	if (buffer != 0) {
371	    if (_nc_curr_token.tk_name == buffer)
372		_nc_curr_token.tk_name = 0;
373	    FreeAndNull(buffer);
374	}
375	return (EOF);
376    }
377
378  start_token:
379    token_start = stream_pos();
380    while ((ch = next_char()) == '\n' || iswhite(ch)) {
381	if (ch == '\n')
382	    had_newline = TRUE;
383	continue;
384    }
385
386    ch = eat_escaped_newline(ch);
387
388#ifdef TRACE
389    old_line = _nc_curr_line;
390    old_col = _nc_curr_col;
391#endif
392    if (ch == EOF)
393	type = EOF;
394    else {
395	/* if this is a termcap entry, skip a leading separator */
396	if (separator == ':' && ch == ':')
397	    ch = next_char();
398
399	if (ch == '.'
400#if NCURSES_EXT_FUNCS
401	    && !_nc_disable_period
402#endif
403	    ) {
404	    dot_flag = TRUE;
405	    DEBUG(8, ("dot-flag set"));
406
407	    while ((ch = next_char()) == '.' || iswhite(ch))
408		continue;
409	}
410
411	if (ch == EOF) {
412	    type = EOF;
413	    goto end_of_token;
414	}
415
416	/* have to make some punctuation chars legal for terminfo */
417	if (!isalnum(UChar(ch))
418#if NCURSES_EXT_FUNCS
419	    && !(ch == '.' && _nc_disable_period)
420#endif
421	    && !strchr(terminfo_punct, (char) ch)) {
422	    if (!silent)
423		_nc_warning("Illegal character (expected alphanumeric or %s) - '%s'",
424			    terminfo_punct, unctrl((chtype) ch));
425	    _nc_panic_mode(separator);
426	    goto start_token;
427	}
428
429	if (buffer == 0)
430	    buffer = typeMalloc(char, MAX_ENTRY_SIZE);
431
432#ifdef TRACE
433	old_line = _nc_curr_line;
434	old_col = _nc_curr_col;
435#endif
436	ptr = buffer;
437	*(ptr++) = ch;
438
439	if (first_column) {
440	    _nc_comment_start = token_start;
441	    _nc_comment_end = _nc_curr_file_pos;
442	    _nc_start_line = _nc_curr_line;
443
444	    _nc_syntax = ERR;
445	    after_name = 0;
446	    after_list = 0;
447	    while ((ch = next_char()) != '\n') {
448		if (ch == EOF) {
449		    _nc_err_abort(MSG_NO_INPUTS);
450		} else if (ch == '|') {
451		    after_list = ptr;
452		    if (after_name == 0)
453			after_name = ptr;
454		} else if (ch == ':' && last_char() != ',') {
455		    _nc_syntax = SYN_TERMCAP;
456		    separator = ':';
457		    break;
458		} else if (ch == ',') {
459		    _nc_syntax = SYN_TERMINFO;
460		    separator = ',';
461		    /*
462		     * If we did not see a '|', then we found a name with no
463		     * aliases or description.
464		     */
465		    if (after_name == 0)
466			break;
467		    /*
468		     * If we see a comma, we assume this is terminfo unless we
469		     * subsequently run into a colon.  But we don't stop
470		     * looking for a colon until hitting a newline.  This
471		     * allows commas to be embedded in description fields of
472		     * either syntax.
473		     */
474		} else
475		    ch = eat_escaped_newline(ch);
476
477		*ptr++ = ch;
478	    }
479	    ptr[0] = '\0';
480	    if (_nc_syntax == ERR) {
481		/*
482		 * Grrr...what we ought to do here is barf, complaining that
483		 * the entry is malformed.  But because a couple of name fields
484		 * in the 8.2 termcap file end with |\, we just have to assume
485		 * it's termcap syntax.
486		 */
487		_nc_syntax = SYN_TERMCAP;
488		separator = ':';
489	    } else if (_nc_syntax == SYN_TERMINFO) {
490		/* throw away trailing /, *$/ */
491		for (--ptr; iswhite(*ptr) || *ptr == ','; ptr--)
492		    continue;
493		ptr[1] = '\0';
494	    }
495
496	    /*
497	     * This is the soonest we have the terminal name fetched.  Set up
498	     * for following warning messages.  If there's no '|', then there
499	     * is no description.
500	     */
501	    if (after_name != 0) {
502		ch = *after_name;
503		*after_name = '\0';
504		_nc_set_type(buffer);
505		*after_name = ch;
506	    }
507
508	    /*
509	     * Compute the boundary between the aliases and the description
510	     * field for syntax-checking purposes.
511	     */
512	    if (after_list != 0) {
513		if (!silent) {
514		    if (*after_list == '\0')
515			_nc_warning("empty longname field");
516		    else if (strchr(after_list, ' ') == 0)
517			_nc_warning("older tic versions may treat the description field as an alias");
518		}
519	    } else {
520		after_list = buffer + strlen(buffer);
521		DEBUG(1, ("missing description"));
522	    }
523
524	    /*
525	     * Whitespace in a name field other than the long name can confuse
526	     * rdist and some termcap tools.  Slashes are a no-no.  Other
527	     * special characters can be dangerous due to shell expansion.
528	     */
529	    for (s = buffer; s < after_list; ++s) {
530		if (isspace(UChar(*s))) {
531		    if (!silent)
532			_nc_warning("whitespace in name or alias field");
533		    break;
534		} else if (*s == '/') {
535		    if (!silent)
536			_nc_warning("slashes aren't allowed in names or aliases");
537		    break;
538		} else if (strchr("$[]!*?", *s)) {
539		    if (!silent)
540			_nc_warning("dubious character `%c' in name or alias field", *s);
541		    break;
542		}
543	    }
544
545	    _nc_curr_token.tk_name = buffer;
546	    type = NAMES;
547	} else {
548	    if (had_newline && _nc_syntax == SYN_TERMCAP) {
549		_nc_warning("Missing backslash before newline");
550		had_newline = FALSE;
551	    }
552	    while ((ch = next_char()) != EOF) {
553		if (!isalnum(UChar(ch))) {
554		    if (_nc_syntax == SYN_TERMINFO) {
555			if (ch != '_')
556			    break;
557		    } else {	/* allow ';' for "k;" */
558			if (ch != ';')
559			    break;
560		    }
561		}
562		*(ptr++) = ch;
563	    }
564
565	    *ptr++ = '\0';
566	    switch (ch) {
567	    case ',':
568	    case ':':
569		if (ch != separator)
570		    _nc_err_abort("Separator inconsistent with syntax");
571		_nc_curr_token.tk_name = buffer;
572		type = BOOLEAN;
573		break;
574	    case '@':
575		if ((ch = next_char()) != separator && !silent)
576		    _nc_warning("Missing separator after `%s', have %s",
577				buffer, unctrl((chtype) ch));
578		_nc_curr_token.tk_name = buffer;
579		type = CANCEL;
580		break;
581
582	    case '#':
583		found = 0;
584		while (isalnum(ch = next_char())) {
585		    numbuf[found++] = ch;
586		    if (found >= sizeof(numbuf) - 1)
587			break;
588		}
589		numbuf[found] = '\0';
590		number = strtol(numbuf, &numchk, 0);
591		if (!silent) {
592		    if (numchk == numbuf)
593			_nc_warning("no value given for `%s'", buffer);
594		    if ((*numchk != '\0') || (ch != separator))
595			_nc_warning("Missing separator");
596		}
597		_nc_curr_token.tk_name = buffer;
598		_nc_curr_token.tk_valnumber = number;
599		type = NUMBER;
600		break;
601
602	    case '=':
603		ch = _nc_trans_string(ptr, buffer + MAX_ENTRY_SIZE);
604		if (!silent && ch != separator)
605		    _nc_warning("Missing separator");
606		_nc_curr_token.tk_name = buffer;
607		_nc_curr_token.tk_valstring = ptr;
608		type = STRING;
609		break;
610
611	    case EOF:
612		type = EOF;
613		break;
614	    default:
615		/* just to get rid of the compiler warning */
616		type = UNDEF;
617		if (!silent)
618		    _nc_warning("Illegal character - '%s'", unctrl((chtype) ch));
619	    }
620	}			/* end else (first_column == FALSE) */
621    }				/* end else (ch != EOF) */
622
623  end_of_token:
624
625#ifdef TRACE
626    if (dot_flag == TRUE)
627	DEBUG(8, ("Commented out "));
628
629    if (_nc_tracing >= DEBUG_LEVEL(8)) {
630	_tracef("parsed %d.%d to %d.%d",
631		old_line, old_col,
632		_nc_curr_line, _nc_curr_col);
633    }
634    if (_nc_tracing >= DEBUG_LEVEL(7)) {
635	switch (type) {
636	case BOOLEAN:
637	    _tracef("Token: Boolean; name='%s'",
638		    _nc_curr_token.tk_name);
639	    break;
640
641	case NUMBER:
642	    _tracef("Token: Number;  name='%s', value=%d",
643		    _nc_curr_token.tk_name,
644		    _nc_curr_token.tk_valnumber);
645	    break;
646
647	case STRING:
648	    _tracef("Token: String;  name='%s', value=%s",
649		    _nc_curr_token.tk_name,
650		    _nc_visbuf(_nc_curr_token.tk_valstring));
651	    break;
652
653	case CANCEL:
654	    _tracef("Token: Cancel; name='%s'",
655		    _nc_curr_token.tk_name);
656	    break;
657
658	case NAMES:
659
660	    _tracef("Token: Names; value='%s'",
661		    _nc_curr_token.tk_name);
662	    break;
663
664	case EOF:
665	    _tracef("Token: End of file");
666	    break;
667
668	default:
669	    _nc_warning("Bad token type");
670	}
671    }
672#endif
673
674    if (dot_flag == TRUE)	/* if commented out, use the next one */
675	type = _nc_get_token(silent);
676
677    DEBUG(3, ("token: `%s', class %d",
678	      ((_nc_curr_token.tk_name != 0)
679	       ? _nc_curr_token.tk_name
680	       : "<null>"),
681	      type));
682
683    return (type);
684}
685
686/*
687 *	char
688 *	trans_string(ptr)
689 *
690 *	Reads characters using next_char() until encountering a separator, nl,
691 *	or end-of-file.  The returned value is the character which caused
692 *	reading to stop.  The following translations are done on the input:
693 *
694 *		^X  goes to  ctrl-X (i.e. X & 037)
695 *		{\E,\n,\r,\b,\t,\f}  go to
696 *			{ESCAPE,newline,carriage-return,backspace,tab,formfeed}
697 *		{\^,\\}  go to  {carat,backslash}
698 *		\ddd (for ddd = up to three octal digits)  goes to the character ddd
699 *
700 *		\e == \E
701 *		\0 == \200
702 *
703 */
704
705NCURSES_EXPORT(int)
706_nc_trans_string(char *ptr, char *last)
707{
708    int count = 0;
709    int number = 0;
710    int i, c;
711    chtype ch, last_ch = '\0';
712    bool ignored = FALSE;
713    bool long_warning = FALSE;
714
715    while ((ch = c = next_char()) != (chtype) separator && c != EOF) {
716	if (ptr == (last - 1))
717	    break;
718	if ((_nc_syntax == SYN_TERMCAP) && c == '\n')
719	    break;
720	if (ch == '^' && last_ch != '%') {
721	    ch = c = next_char();
722	    if (c == EOF)
723		_nc_err_abort(MSG_NO_INPUTS);
724
725	    if (!(is7bits(ch) && isprint(ch))) {
726		_nc_warning("Illegal ^ character - '%s'", unctrl(ch));
727	    }
728	    if (ch == '?') {
729		*(ptr++) = '\177';
730		if (_nc_tracing)
731		    _nc_warning("Allow ^? as synonym for \\177");
732	    } else {
733		if ((ch &= 037) == 0)
734		    ch = 128;
735		*(ptr++) = (char) (ch);
736	    }
737	} else if (ch == '\\') {
738	    ch = c = next_char();
739	    if (c == EOF)
740		_nc_err_abort(MSG_NO_INPUTS);
741
742	    if (ch >= '0' && ch <= '7') {
743		number = ch - '0';
744		for (i = 0; i < 2; i++) {
745		    ch = c = next_char();
746		    if (c == EOF)
747			_nc_err_abort(MSG_NO_INPUTS);
748
749		    if (c < '0' || c > '7') {
750			if (isdigit(c)) {
751			    _nc_warning("Non-octal digit `%c' in \\ sequence", c);
752			    /* allow the digit; it'll do less harm */
753			} else {
754			    push_back((char) c);
755			    break;
756			}
757		    }
758
759		    number = number * 8 + c - '0';
760		}
761
762		if (number == 0)
763		    number = 0200;
764		*(ptr++) = (char) number;
765	    } else {
766		switch (c) {
767		case 'E':
768		case 'e':
769		    *(ptr++) = '\033';
770		    break;
771
772		case 'a':
773		    *(ptr++) = '\007';
774		    break;
775
776		case 'l':
777		case 'n':
778		    *(ptr++) = '\n';
779		    break;
780
781		case 'r':
782		    *(ptr++) = '\r';
783		    break;
784
785		case 'b':
786		    *(ptr++) = '\010';
787		    break;
788
789		case 's':
790		    *(ptr++) = ' ';
791		    break;
792
793		case 'f':
794		    *(ptr++) = '\014';
795		    break;
796
797		case 't':
798		    *(ptr++) = '\t';
799		    break;
800
801		case '\\':
802		    *(ptr++) = '\\';
803		    break;
804
805		case '^':
806		    *(ptr++) = '^';
807		    break;
808
809		case ',':
810		    *(ptr++) = ',';
811		    break;
812
813		case ':':
814		    *(ptr++) = ':';
815		    break;
816
817		case '\n':
818		    continue;
819
820		default:
821		    _nc_warning("Illegal character '%s' in \\ sequence",
822				unctrl(ch));
823		    /* FALLTHRU */
824		case '|':
825		    *(ptr++) = (char) ch;
826		}		/* endswitch (ch) */
827	    }			/* endelse (ch < '0' ||  ch > '7') */
828	}
829	/* end else if (ch == '\\') */
830	else if (ch == '\n' && (_nc_syntax == SYN_TERMINFO)) {
831	    /*
832	     * Newlines embedded in a terminfo string are ignored, provided
833	     * that the next line begins with whitespace.
834	     */
835	    ignored = TRUE;
836	} else {
837	    *(ptr++) = (char) ch;
838	}
839
840	if (!ignored) {
841	    if (_nc_curr_col <= 1) {
842		push_back(ch);
843		ch = '\n';
844		break;
845	    }
846	    last_ch = ch;
847	    count++;
848	}
849	ignored = FALSE;
850
851	if (count > MAXCAPLEN && !long_warning) {
852	    _nc_warning("Very long string found.  Missing separator?");
853	    long_warning = TRUE;
854	}
855    }				/* end while */
856
857    *ptr = '\0';
858
859    return (ch);
860}
861
862/*
863 *	_nc_push_token()
864 *
865 *	Push a token of given type so that it will be reread by the next
866 *	get_token() call.
867 */
868
869NCURSES_EXPORT(void)
870_nc_push_token(int tokclass)
871{
872    /*
873     * This implementation is kind of bogus, it will fail if we ever do more
874     * than one pushback at a time between get_token() calls.  It relies on the
875     * fact that _nc_curr_token is static storage that nothing but
876     * _nc_get_token() touches.
877     */
878    pushtype = tokclass;
879    if (pushname == 0)
880	pushname = typeMalloc(char, MAX_NAME_SIZE + 1);
881    _nc_get_type(pushname);
882
883    DEBUG(3, ("pushing token: `%s', class %d",
884	      ((_nc_curr_token.tk_name != 0)
885	       ? _nc_curr_token.tk_name
886	       : "<null>"),
887	      pushtype));
888}
889
890/*
891 * Panic mode error recovery - skip everything until a "ch" is found.
892 */
893NCURSES_EXPORT(void)
894_nc_panic_mode(char ch)
895{
896    int c;
897
898    for (;;) {
899	c = next_char();
900	if (c == ch)
901	    return;
902	if (c == EOF)
903	    return;
904    }
905}
906
907#if NO_LEAKS
908NCURSES_EXPORT(void)
909_nc_comp_scan_leaks(void)
910{
911    FreeAndNull(pushname);
912}
913#endif
914