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