1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * esclex.c -- lexer for esc
26  *
27  * this module provides lexical analysis and error handling routine
28  * expected by the yacc-generated parser (i.e. yylex() and yyerror()).
29  * it also does lots of tracking of things like filenames, line numbers,
30  * and what tokens are seen on a line up to the point where a syntax error
31  * was found.  this module also arranges for the input source files to
32  * be run through cpp.
33  */
34 
35 #pragma ident	"%Z%%M%	%I%	%E% SMI"
36 
37 #include <stdio.h>
38 #include <ctype.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <unistd.h>
42 #include <time.h>
43 #include <errno.h>
44 #include "out.h"
45 #include "alloc.h"
46 #include "stats.h"
47 #include "stable.h"
48 #include "lut.h"
49 #include "literals.h"
50 #include "tree.h"
51 #include "esclex.h"
52 #include "eftread.h"
53 #include "check.h"
54 #include "y.tab.h"
55 
56 /* ridiculously long token buffer -- disallow any token longer than this */
57 #define	MAXTOK	8192
58 static char Tok[MAXTOK];
59 
60 /* some misc stats we keep on the lexer & parser */
61 static struct stats *Tokcount;
62 static struct stats *Lexelapse;
63 struct stats *Filecount;
64 struct filestats {
65 	struct filestats *next;
66 	struct stats *stats;
67 	struct stats *idstats;
68 } *Fstats;
69 
70 static int Errcount;
71 
72 /* input file state */
73 static char **Files;
74 static const char *Fileopened;
75 static FILE *Fp;
76 static int Line;
77 static const char *File;
78 static const char *Cpp = "/usr/bin/cpp";
79 #ifdef	ESC
80 static const char *Cppargs;
81 static const char *Cppstdargs = "-undef -Y.";
82 #endif	/* ESC */
83 
84 /* for debugging */
85 static int Lexecho;	/* echo tokens as we read them */
86 
87 /* forward declarations of our internal routines */
88 static int record(int tok, const char *s);
89 static void dumpline(int flags);
90 static void doident();
91 static void dopragma(const char *tok);
92 
93 /*
94  * table of reserved words.  this table is only used by lex_init()
95  * to intialize the Rwords lookup table.
96  */
97 static const struct {
98 	const char *word;
99 	const int val;
100 } Rwords[] = {
101 	{ "asru", ASRU },
102 	{ "count", COUNT },
103 	{ "div", DIV },
104 	{ "engine", ENGINE },
105 	{ "event", EVENT },
106 	{ "fru", FRU },
107 	{ "if", IF },
108 	{ "mask", MASK },
109 	{ "prop", PROP },
110 	{ "config", CONFIG },
111 	/*
112 	 * PATHFUNC indicates functions that operate only on paths
113 	 * and quotes
114 	 */
115 	{ "is_connected", PATHFUNC },
116 	{ "is_under", PATHFUNC },
117 };
118 
119 /*
120  * Rwordslut is a lookup table of reserved words.  lhs is the word
121  * (in the string table) and the rhs is the token value returned
122  * by the yylex() for that word.
123  */
124 static struct lut *Rwordslut;
125 
126 static const struct {
127 	const char *suffix;
128 	const unsigned long long nsec;
129 } Timesuffix[] = {
130 	{ "nanosecond",		1ULL },
131 	{ "nanoseconds",	1ULL },
132 	{ "nsec",		1ULL },
133 	{ "nsecs",		1ULL },
134 	{ "ns",			1ULL },
135 	{ "microsecond",	1000ULL },
136 	{ "microseconds",	1000ULL },
137 	{ "usec",		1000ULL },
138 	{ "usecs",		1000ULL },
139 	{ "us",			1000ULL },
140 	{ "millisecond",	1000000ULL },
141 	{ "milliseconds",	1000000ULL },
142 	{ "msec",		1000000ULL },
143 	{ "msecs",		1000000ULL },
144 	{ "ms",			1000000ULL },
145 	{ "second",		1000000000ULL },
146 	{ "seconds",		1000000000ULL },
147 	{ "s",			1000000000ULL },
148 	{ "minute",		1000000000ULL * 60 },
149 	{ "minutes",		1000000000ULL * 60 },
150 	{ "min",		1000000000ULL * 60 },
151 	{ "mins",		1000000000ULL * 60 },
152 	{ "m",			1000000000ULL * 60 },
153 	{ "hour",		1000000000ULL * 60 * 60 },
154 	{ "hours",		1000000000ULL * 60 * 60 },
155 	{ "hr",			1000000000ULL * 60 * 60 },
156 	{ "hrs",		1000000000ULL * 60 * 60 },
157 	{ "h",			1000000000ULL * 60 * 60 },
158 	{ "day",		1000000000ULL * 60 * 60 * 24 },
159 	{ "days",		1000000000ULL * 60 * 60 * 24 },
160 	{ "d",			1000000000ULL * 60 * 60 * 24 },
161 	{ "week",		1000000000ULL * 60 * 60 * 24 * 7 },
162 	{ "weeks",		1000000000ULL * 60 * 60 * 24 * 7 },
163 	{ "wk",			1000000000ULL * 60 * 60 * 24 * 7 },
164 	{ "wks",		1000000000ULL * 60 * 60 * 24 * 7 },
165 	{ "month",		1000000000ULL * 60 * 60 * 24 * 30 },
166 	{ "months",		1000000000ULL * 60 * 60 * 24 * 30 },
167 	{ "year",		1000000000ULL * 60 * 60 * 24 * 365 },
168 	{ "years",		1000000000ULL * 60 * 60 * 24 * 365 },
169 	{ "yr",			1000000000ULL * 60 * 60 * 24 * 365 },
170 	{ "yrs",		1000000000ULL * 60 * 60 * 24 * 365 },
171 };
172 
173 /*
174  * some wrappers around the general lut functions to provide type checking...
175  */
176 
177 static struct lut *
178 lex_s2i_lut_add(struct lut *root, const char *s, intptr_t i)
179 {
180 	return (lut_add(root, (void *)s, (void *)i, NULL));
181 }
182 
183 static int
184 lex_s2i_lut_lookup(struct lut *root, const char *s)
185 {
186 	return ((intptr_t)lut_lookup(root, (void *)s, NULL));
187 }
188 
189 static struct lut *
190 lex_s2ullp_lut_add(struct lut *root, const char *s,
191     const unsigned long long *ullp)
192 {
193 	return (lut_add(root, (void *)s, (void *)ullp, NULL));
194 }
195 
196 const unsigned long long *
197 lex_s2ullp_lut_lookup(struct lut *root, const char *s)
198 {
199 	return ((unsigned long long *)lut_lookup(root, (void *)s, NULL));
200 }
201 
202 /*
203  * lex_init -- initialize the lexer with appropriate filenames & debug flags
204  */
205 
206 /*ARGSUSED*/
207 void
208 lex_init(char **av, const char *cppargs, int lexecho)
209 {
210 	int i;
211 #ifdef	ESC
212 	const char *ptr;
213 #endif	/* ESC */
214 
215 	Lexecho = lexecho;
216 	Tokcount = stats_new_counter("lex.tokens", "total tokens in", 1);
217 	Filecount = stats_new_counter("lex.files", "total files read", 0);
218 	Lexelapse = stats_new_elapse("lex.time", "elapsed lex/parse time", 1);
219 
220 #ifdef	ESC
221 	Cppargs = cppargs;
222 
223 	/* allow user to tell us where cpp is if it is some weird place */
224 	if (ptr = getenv("_ESC_CPP"))
225 		Cpp = ptr;
226 
227 	/* and in case it takes some special stdargs */
228 	if (ptr = getenv("_ESC_CPP_STDARGS"))
229 		Cppstdargs = ptr;
230 
231 	/* verify we can find cpp */
232 	if (access(Cpp, X_OK) < 0) {
233 		Cpp = "/usr/lib/cpp";
234 		if (access(Cpp, X_OK) < 0)
235 			out(O_DIE, "can't locate cpp");
236 	}
237 #endif	/* ESC */
238 
239 	Files = av;
240 
241 	/* verify we can find all the input files */
242 	while (*av) {
243 		if (strlen(*av) >= MAXTOK - strlen(Cpp) - 3)
244 			out(O_DIE, "filename too long: %.100s...", *av);
245 		if (access(*av, R_OK) < 0)
246 			out(O_DIE|O_SYS, "%s", *av);
247 		av++;
248 		stats_counter_bump(Filecount);
249 	}
250 
251 	/* put reserved words into the string table & a lookup table */
252 	for (i = 0; i < sizeof (Rwords) / sizeof (*Rwords); i++)
253 		Rwordslut = lex_s2i_lut_add(Rwordslut,
254 		    stable(Rwords[i].word), Rwords[i].val);
255 
256 	/* initialize table of timeval suffixes */
257 	for (i = 0; i < sizeof (Timesuffix) / sizeof (*Timesuffix); i++) {
258 		Timesuffixlut = lex_s2ullp_lut_add(Timesuffixlut,
259 		    stable(Timesuffix[i].suffix), &Timesuffix[i].nsec);
260 	}
261 
262 	/* record start time */
263 	stats_elapse_start(Lexelapse);
264 }
265 
266 void
267 closefile(void)
268 {
269 	if (Fp != NULL) {
270 #ifdef	ESC
271 		if (pclose(Fp) > 0)
272 			out(O_DIE, "cpp errors while reading \"%s\", "
273 			    "bailing out.", Fileopened);
274 #else
275 		(void) fclose(Fp);
276 #endif	/* ESC */
277 	}
278 	Fp = NULL;
279 }
280 
281 /*
282  * yylex -- the lexer, called yylex() because that's what yacc wants
283  */
284 
285 int
286 yylex()
287 {
288 	int c;
289 	int nextc;
290 	char *ptr = Tok;
291 	char *eptr = &Tok[MAXTOK];
292 	const char *cptr;
293 	int startline;
294 	int val;
295 	static int bol = 1;	/* true if we're at beginning of line */
296 
297 	for (;;) {
298 		while (Fp == NULL) {
299 			char ibuf[80];
300 
301 			if (*Files == NULL)
302 				return (record(EOF, NULL));
303 			Fileopened = stable(*Files++);
304 #ifdef	ESC
305 			sprintf(Tok, "%s %s %s %s",
306 			    Cpp, Cppstdargs, Cppargs, Fileopened);
307 			if ((Fp = popen(Tok, "r")) == NULL)
308 				out(O_DIE|O_SYS, "%s", Tok);
309 #else
310 			Fp = eftread_fopen(Fileopened, ibuf, sizeof (ibuf));
311 #endif	/* ESC */
312 			Line = 1;
313 			bol = 1;
314 
315 			/* add name to stats for visibility */
316 			if (Fp != NULL) {
317 				static int fnum;
318 				char nbuf[100];
319 				struct filestats *nfs = MALLOC(sizeof (*nfs));
320 
321 				(void) sprintf(nbuf, "lex.file%d", fnum);
322 				nfs->stats = stats_new_string(nbuf, "", 0);
323 				stats_string_set(nfs->stats, Fileopened);
324 
325 				if (ibuf[0] != '\0') {
326 					(void) sprintf(nbuf, "lex.file%d-ident",
327 					    fnum);
328 					nfs->idstats =
329 					    stats_new_string(nbuf, "", 0);
330 					stats_string_set(nfs->idstats, ibuf);
331 				} else {
332 					nfs->idstats = NULL;
333 				}
334 
335 				nfs->next = Fstats;
336 				Fstats = nfs;
337 				fnum++;
338 			}
339 		}
340 
341 		switch (c = getc(Fp)) {
342 		case '#':
343 			/* enforce that we're at beginning of line */
344 			if (!bol)
345 				return (record(c, NULL));
346 
347 			while ((c = getc(Fp)) != EOF &&
348 			    (c == ' ' || c == '\t'))
349 				;
350 			if (!isdigit(c)) {
351 				/*
352 				 * three cases here:
353 				 *	#pragma
354 				 *	#ident
355 				 *	#something-we-don't-understand
356 				 * anything we don't expect we just ignore.
357 				 */
358 				*ptr++ = c;
359 				while ((c = getc(Fp)) != EOF && isalnum(c))
360 					if (ptr < eptr - 1)
361 						*ptr++ = c;
362 				*ptr++ = '\0';
363 				if (strcmp(Tok, "pragma") == 0) {
364 					/* skip white space */
365 					while ((c = getc(Fp)) != EOF &&
366 					    (c == ' ' || c == '\t'))
367 						;
368 
369 					if (c == EOF || c == '\n')
370 						outfl(O_DIE, File, Line,
371 						    "bad #pragma");
372 
373 					/* pull in next token */
374 					ptr = Tok;
375 					*ptr++ = c;
376 					while ((c = getc(Fp)) != EOF &&
377 					    !isspace(c))
378 						if (ptr < eptr - 1)
379 							*ptr++ = c;
380 					*ptr++ = '\0';
381 					(void) ungetc(c, Fp);
382 
383 					dopragma(Tok);
384 				} else if (strcmp(Tok, "ident") == 0)
385 					doident();
386 			} else {
387 				/* handle file & line info from cpp */
388 				Line = 0;
389 				do {
390 					if (!isdigit(c))
391 						break;
392 					Line = Line * 10 + c - '0';
393 				} while ((c = getc(Fp)) != EOF);
394 				Line--;		/* newline will increment it */
395 				while (c != EOF && isspace(c))
396 					c = getc(Fp);
397 				if (c != '"')
398 					outfl(O_DIE, File, Line,
399 					    "bad # statement (file name)");
400 				while ((c = getc(Fp)) != EOF && c != '"')
401 					if (ptr < eptr - 1)
402 						*ptr++ = c;
403 				*ptr++ = '\0';
404 				if (c != '"')
405 					outfl(O_DIE, File, Line,
406 					    "bad # statement (quotes)");
407 				File = stable(Tok);
408 			}
409 			/* skip the rest of the cpp line */
410 			while ((c = getc(Fp)) != EOF && c != '\n' && c != '\r')
411 				;
412 			if (c == EOF)
413 				return (record(c, NULL));
414 			else
415 				(void) ungetc(c, Fp);
416 			ptr = Tok;
417 			break;
418 
419 		case EOF:
420 			closefile();
421 			continue;
422 
423 		case '\n':
424 			Line++;
425 			bol = 1;
426 			break;
427 
428 		case '\r':
429 		case ' ':
430 		case '\t':
431 			bol = 0;
432 			break;
433 
434 		case '/':
435 			bol = 0;
436 			/* comment handling */
437 			if ((nextc = getc(Fp)) == EOF)
438 				outfl(O_DIE, File, Line, "unexpected EOF");
439 			else if (nextc == '*') {
440 				startline = Line;
441 				while ((c = getc(Fp)) != EOF) {
442 					if (c == '\n')
443 						Line++;
444 					else if (c == '*' &&
445 					    (((c = getc(Fp)) == EOF) ||
446 					    (c == '/')))
447 						break;
448 				}
449 				if (c == EOF) {
450 					outfl(O_DIE, File, Line,
451 					    "end of comment not seen "
452 					    "(started on line %d)",
453 					    startline);
454 				}
455 			} else {
456 				/* wasn't a comment, return the '/' token */
457 				(void) ungetc(nextc, Fp);
458 				return (record(c, NULL));
459 			}
460 			break;
461 
462 		case '"': {
463 			int prevc;
464 
465 			bol = 0;
466 			prevc = '\0';
467 			/* quoted string handling */
468 			startline = Line;
469 			for (;;) {
470 				c = getc(Fp);
471 				if (c == EOF)
472 					outfl(O_DIE, File, Line,
473 					    "end of string not seen "
474 					    "(started on line %d)",
475 					    startline);
476 				else if (c == '\n')
477 					Line++;
478 				else if (c == '"' && prevc != '\\')
479 					break;
480 				else if (ptr < eptr)
481 					*ptr++ = c;
482 				prevc = c;
483 			}
484 			if (ptr >= eptr)
485 				out(O_DIE, File, Line, "string too long");
486 			*ptr++ = '\0';
487 			return (record(QUOTE, stable(Tok)));
488 		}
489 		case '&':
490 			bol = 0;
491 			/* && */
492 			if ((nextc = getc(Fp)) == '&')
493 				return (record(AND, NULL));
494 			else {
495 				(void) ungetc(nextc, Fp);
496 				return (record(c, NULL));
497 			}
498 			/*NOTREACHED*/
499 			break;
500 
501 		case '|':
502 			bol = 0;
503 			/* || */
504 			if ((nextc = getc(Fp)) == '|')
505 				return (record(OR, NULL));
506 			else {
507 				(void) ungetc(nextc, Fp);
508 				return (record(c, NULL));
509 			}
510 			/*NOTREACHED*/
511 			break;
512 
513 		case '!':
514 			bol = 0;
515 			/* ! or != */
516 			if ((nextc = getc(Fp)) == '=')
517 				return (record(NE, NULL));
518 			else {
519 				(void) ungetc(nextc, Fp);
520 				return (record(c, NULL));
521 			}
522 			/*NOTREACHED*/
523 			break;
524 
525 		case '=':
526 			bol = 0;
527 			/* == */
528 			if ((nextc = getc(Fp)) == '=')
529 				return (record(EQ, NULL));
530 			else {
531 				(void) ungetc(nextc, Fp);
532 				return (record(c, NULL));
533 			}
534 			/*NOTREACHED*/
535 			break;
536 
537 		case '-':
538 			bol = 0;
539 			/* -> */
540 			if ((nextc = getc(Fp)) == '>')
541 				return (record(ARROW, stable(Tok)));
542 			else {
543 				(void) ungetc(nextc, Fp);
544 				return (record(c, NULL));
545 			}
546 			/*NOTREACHED*/
547 			break;
548 
549 		case '<':
550 			bol = 0;
551 			if ((nextc = getc(Fp)) == '=')
552 				/* <= */
553 				return (record(LE, NULL));
554 			else if (nextc == '<')
555 				/* << */
556 				return (record(LSHIFT, NULL));
557 			else {
558 				(void) ungetc(nextc, Fp);
559 				return (record(c, NULL));
560 			}
561 			/*NOTREACHED*/
562 			break;
563 
564 		case '>':
565 			bol = 0;
566 			if ((nextc = getc(Fp)) == '=')
567 				/* >= */
568 				return (record(GE, NULL));
569 			else if (nextc == '>')
570 				/* >> */
571 				return (record(RSHIFT, NULL));
572 			else {
573 				(void) ungetc(nextc, Fp);
574 				return (record(c, NULL));
575 			}
576 			/*NOTREACHED*/
577 			break;
578 
579 		default:
580 			bol = 0;
581 			if (isdigit(c)) {
582 				int base;
583 
584 				/* collect rest of number */
585 				if (c == '0') {
586 					*ptr++ = c;
587 					if ((c = getc(Fp)) == EOF) {
588 						*ptr++ = '\0';
589 						return (record(NUMBER,
590 						    stable(Tok)));
591 					} else if (c == 'x' || c == 'X') {
592 						*ptr++ = c;
593 						base = 16;
594 					} else {
595 						(void) ungetc(c, Fp);
596 						base = 8;
597 					}
598 				} else {
599 					*ptr++ = c;
600 					base = 10;
601 				}
602 				while ((c = getc(Fp)) != EOF) {
603 					if (ptr >= eptr)
604 						out(O_DIE, File, Line,
605 						    "number too long");
606 
607 					switch (base) {
608 					case 16:
609 						if (c >= 'a' && c <= 'f' ||
610 						    c >= 'A' && c <= 'F') {
611 							*ptr++ = c;
612 							continue;
613 						}
614 						/*FALLTHRU*/
615 					case 10:
616 						if (c >= '8' && c <= '9') {
617 							*ptr++ = c;
618 							continue;
619 						}
620 						/*FALLTHRU*/
621 					case 8:
622 						if (c >= '0' && c <= '7') {
623 							*ptr++ = c;
624 							continue;
625 						}
626 						/* not valid for this base */
627 						*ptr++ = '\0';
628 						(void) ungetc(c, Fp);
629 						return (record(NUMBER,
630 						    stable(Tok)));
631 					}
632 				}
633 				*ptr++ = '\0';
634 				return (record(NUMBER, stable(Tok)));
635 			} else if (isalpha(c)) {
636 				/* collect identifier */
637 				*ptr++ = c;
638 				for (;;) {
639 					c = getc(Fp);
640 					if ((isalnum(c) || c == '_') &&
641 					    ptr < eptr)
642 						*ptr++ = c;
643 					else {
644 						(void) ungetc(c, Fp);
645 						break;
646 					}
647 				}
648 				if (ptr >= eptr)
649 					out(O_DIE, File, Line,
650 					    "identifier too long");
651 				*ptr++ = '\0';
652 				cptr = stable(Tok);
653 				if (val = lex_s2i_lut_lookup(Rwordslut, cptr)) {
654 					return (record(val, cptr));
655 				}
656 				return (record(ID, cptr));
657 			} else
658 				return (record(c, NULL));
659 		}
660 		/*NOTREACHED*/
661 	}
662 }
663 
664 /*
665  * the record()/dumpline() routines are used to track & report
666  * the list of tokens seen on a given line.  this is used in two ways.
667  * first, syntax errors found by the parser are reported by us (via
668  * yyerror()) and we tack on the tokens processed so far on the current
669  * line to help indicate exactly where the error is.  second, if "lexecho"
670  * debugging is turned on, these routines provide it.
671  */
672 #define	MAXRECORD 1000
673 static int Recordedline;
674 static struct {
675 	int tok;
676 	const char *s;
677 } Recorded[MAXRECORD];
678 static int Recordnext;
679 
680 static int
681 record(int tok, const char *s)
682 {
683 	stats_counter_bump(Tokcount);
684 	if (Line != Recordedline) {
685 		/* starting new line, dump out the previous line */
686 		if (Lexecho && Recordedline) {
687 			outfl(O_NONL, File, Recordedline, "lex: ");
688 			dumpline(O_OK);
689 		}
690 		Recordedline = Line;
691 		Recordnext = 0;
692 	}
693 	if (Recordnext >= MAXRECORD)
694 		outfl(O_DIE, File, Line, "line too long, bailing out");
695 	Recorded[Recordnext].tok = tok;
696 	Recorded[Recordnext++].s = s;
697 
698 	yylval.tok.s = s;
699 	yylval.tok.file = File;
700 	yylval.tok.line = Line;
701 	return (tok);
702 }
703 
704 /*ARGSUSED*/
705 static void
706 dumpline(int flags)
707 {
708 	int i;
709 
710 	for (i = 0; i < Recordnext; i++)
711 		if (Recorded[i].s && Recorded[i].tok != ARROW)
712 			switch (Recorded[i].tok) {
713 			case T_QUOTE:
714 				out(flags|O_NONL, " \"%s\"",
715 				    Recorded[i].s);
716 				break;
717 
718 			default:
719 				out(flags|O_NONL, " %s",
720 				    Recorded[i].s);
721 				break;
722 			}
723 		else
724 			switch (Recorded[i].tok) {
725 			case EOF:
726 				out(flags|O_NONL, " EOF");
727 				break;
728 			case ARROW:
729 				out(flags|O_NONL, " ->%s",
730 				    Recorded[i].s);
731 				break;
732 			case EQ:
733 				out(flags|O_NONL, " ==");
734 				break;
735 			case NE:
736 				out(flags|O_NONL, " !=");
737 				break;
738 			case OR:
739 				out(flags|O_NONL, " ||");
740 				break;
741 			case AND:
742 				out(flags|O_NONL, " &&");
743 				break;
744 			case LE:
745 				out(flags|O_NONL, " <=");
746 				break;
747 			case GE:
748 				out(flags|O_NONL, " >=");
749 				break;
750 			case LSHIFT:
751 				out(flags|O_NONL, " <<");
752 				break;
753 			case RSHIFT:
754 				out(flags|O_NONL, " >>");
755 				break;
756 			default:
757 				if (isprint(Recorded[i].tok))
758 					out(flags|O_NONL, " %c",
759 					    Recorded[i].tok);
760 				else
761 					out(flags|O_NONL, " '\\%03o'",
762 					    Recorded[i].tok);
763 				break;
764 			}
765 	out(flags, NULL);
766 }
767 
768 /*
769  * yyerror -- report a pareser error, called yyerror because yacc wants it
770  */
771 
772 void
773 yyerror(const char *s)
774 {
775 	Errcount++;
776 	outfl(O_ERR|O_NONL, File, Line, "%s, tokens: ", s);
777 	dumpline(O_ERR);
778 }
779 
780 /*
781  * doident -- handle "#pragma ident" directives
782  */
783 static void
784 doident()
785 {
786 	int c;
787 	char *ptr = Tok;
788 	char *eptr = &Tok[MAXTOK];
789 
790 	/* skip white space and quotes */
791 	while ((c = getc(Fp)) != EOF &&
792 	    (c == ' ' || c == '\t' || c == '"'))
793 		;
794 
795 	if (c == EOF || c == '\n')
796 		outfl(O_DIE, File, Line, "bad ident");
797 
798 	/* pull in next token */
799 	ptr = Tok;
800 	*ptr++ = c;
801 	while ((c = getc(Fp)) != EOF && c != '"' && c != '\n')
802 		if (ptr < eptr - 1)
803 			*ptr++ = c;
804 	*ptr++ = '\0';
805 	if (c != '\n') {
806 		/* skip to end of line (including close quote, if any) */
807 		while ((c = getc(Fp)) != EOF && c != '\n')
808 			;
809 	}
810 	(void) ungetc(c, Fp);
811 	Ident = lut_add(Ident, (void *)stable(Tok), (void *)0, NULL);
812 
813 	outfl(O_VERB, File, Line, "pragma set: ident \"%s\"", Tok);
814 }
815 
816 /*
817  * dodictionary -- handle "#pragma dictionary" directives
818  */
819 static void
820 dodictionary()
821 {
822 	int c;
823 	char *ptr = Tok;
824 	char *eptr = &Tok[MAXTOK];
825 
826 	/* skip white space and quotes */
827 	while ((c = getc(Fp)) != EOF &&
828 	    (c == ' ' || c == '\t' || c == '"'))
829 		;
830 
831 	if (c == EOF || c == '\n')
832 		outfl(O_DIE, File, Line, "bad dictionary");
833 
834 	/* pull in next token */
835 	ptr = Tok;
836 	*ptr++ = c;
837 	while ((c = getc(Fp)) != EOF && c != '"' && c != '\n')
838 		if (ptr < eptr - 1)
839 			*ptr++ = c;
840 	*ptr++ = '\0';
841 	if (c != '\n') {
842 		/* skip to end of line (including close quote, if any) */
843 		while ((c = getc(Fp)) != EOF && c != '\n')
844 			;
845 	}
846 	(void) ungetc(c, Fp);
847 	Dicts = lut_add(Dicts, (void *)stable(Tok), (void *)0, NULL);
848 
849 	outfl(O_VERB, File, Line, "pragma set: dictionary \"%s\"", Tok);
850 }
851 
852 /*
853  * doallow_cycles -- handle "#pragma allow_cycles" directives
854  */
855 static void
856 doallow_cycles()
857 {
858 	int c;
859 	char *ptr = Tok;
860 	char *eptr = &Tok[MAXTOK];
861 	unsigned long long newlevel;
862 
863 	/*
864 	 * by default the compiler does not allow cycles or loops
865 	 * in propagations.  when cycles are encountered, the
866 	 * compiler prints out an error message.
867 	 *
868 	 *   "#pragma allow_cycles" and
869 	 *   "#pragma allow_cycles 0"
870 	 * allow cycles, but any such cycle will produce a warning
871 	 * message.
872 	 *
873 	 *   "#pragma allow_cycles N"
874 	 * with N > 0 will allow cycles and not produce any
875 	 * warning messages.
876 	 */
877 
878 	/* skip white space and quotes */
879 	while ((c = getc(Fp)) != EOF &&
880 	    (c == ' ' || c == '\t' || c == '"'))
881 		;
882 
883 	if (c == EOF || c == '\n')
884 		newlevel = 0ULL;
885 	else {
886 
887 		/* pull in next token */
888 		ptr = Tok;
889 		*ptr++ = c;
890 		while ((c = getc(Fp)) != EOF && c != '"' && c != '\n')
891 			if (ptr < eptr - 1)
892 				*ptr++ = c;
893 		*ptr++ = '\0';
894 		if (c != '\n') {
895 			/* skip to end of line */
896 			while ((c = getc(Fp)) != EOF && c != '\n')
897 				;
898 		}
899 		newlevel = strtoll(Tok, NULL, 0);
900 	}
901 	(void) ungetc(c, Fp);
902 
903 	(void) check_cycle_level(newlevel);
904 	outfl(O_VERB, File, Line,
905 	    "pragma set: allow_cycles (%s)",
906 	    newlevel ? "no warnings" : "with warnings");
907 }
908 
909 /*
910  * dopragma -- handle #pragma directives
911  */
912 static void
913 dopragma(const char *tok)
914 {
915 	if (strcmp(tok, "ident") == 0)
916 		doident();
917 	else if (strcmp(tok, "dictionary") == 0)
918 		dodictionary();
919 	else if (strcmp(tok, "new_errors_only") == 0) {
920 		if (Pragma_new_errors_only++ == 0)
921 			outfl(O_VERB, File, Line,
922 			    "pragma set: new_errors_only");
923 	} else if (strcmp(tok, "trust_ereports") == 0) {
924 		if (Pragma_trust_ereports++ == 0)
925 			outfl(O_VERB, File, Line,
926 			    "pragma set: trust_ereports");
927 	} else if (strcmp(tok, "allow_cycles") == 0)
928 		doallow_cycles();
929 	else
930 		outfl(O_VERB, File, Line,
931 		    "unknown pragma ignored: \"%s\"", tok);
932 }
933 
934 /*
935  * lex_fini -- finalize the lexer
936  */
937 
938 int
939 lex_fini(void)
940 {
941 	stats_elapse_stop(Lexelapse);
942 	closefile();
943 	if (Lexecho) {
944 		outfl(O_OK, File, Line, "lex: ");
945 		dumpline(O_OK);
946 	}
947 	return (Errcount);
948 }
949 
950 void
951 lex_free(void)
952 {
953 	struct filestats *nfstats = Fstats;
954 
955 	/*
956 	 * Free up memory consumed by the lexer
957 	 */
958 	stats_delete(Tokcount);
959 	stats_delete(Filecount);
960 	stats_delete(Lexelapse);
961 	while (nfstats != NULL) {
962 		Fstats = nfstats->next;
963 		stats_delete(nfstats->stats);
964 		if (nfstats->idstats != NULL)
965 			stats_delete(nfstats->idstats);
966 		FREE(nfstats);
967 		nfstats = Fstats;
968 	}
969 	lut_free(Timesuffixlut, NULL, NULL);
970 	lut_free(Rwordslut, NULL, NULL);
971 	lut_free(Ident, NULL, NULL);
972 	lut_free(Dicts, NULL, NULL);
973 }
974