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