#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <ctype.h>
#include "lexsyms.h"

/*
 * This is version 0.43 of genksyms.c (BETA)
 *
 * In this source, the crc and findsym functions are:
 *
 * updcrc32 function	from gzip (see also the source: makecrc32.c)
 * findsym function	from insmod
 *			(function written by Jon Tombs and/or Bas Laarhoven)
 * 
 * The rest of this source is copyright (C) 1994, Bjorn Ekwall <bj0rn@blox.se>
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2, or (at your option)
 *	any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 *
 */

char *copyright = "(C) 1994, 1995 Bjorn Ekwall <bj0rn@blox.se>, version 0.43(BETA)";

/*
 * If your flex generates a scanner where yytext is
 *	"char *yytext"
 * instead of
 *	"char yytext[YYLMAX]"
 * then add "-DYYTEXT_PTR" to CFLAGS in the Makefile
 */

#ifdef YYTEXT_PTR
extern char *yytext;
#else
extern char yytext[];
#endif
extern int yylex(void);

FILE *logfile;
int line_no;
int debug;
int dump_defs;
int warnings = 0; /* at least for ALPHA versions, use '-q' at your own risk */
char *progname;
char *expand_key;
char *outfile_dir = ".";
int outfile_flag = 0;
FILE *outfile = (FILE *)0;
char outfilename[PATH_MAX];
char infile[PATH_MAX];
char macroname[PATH_MAX];
int use_globals = 0;

#define MAXTMP 10000
static char tmpbuffer[MAXTMP];
static char *pbuffer = tmpbuffer;

char *header[] = {
"/**** This file is generated by genksyms  DO NOT EDIT! ****/",
"#if (defined(CONFIG_MODVERSIONS) || defined(MODVERSIONS)) && !defined(__GENKSYMS__)",
0
};

#define MARK '#' /* kludge to mark unresolved references while parsing */

struct symbol {
	char *key;
	char *value;
	struct symbol *child[2];
	struct symbol *trail;
};

void show_globals(struct symbol *);

struct symbol *trail;
struct symbol *type_root;	/* for typdef */
struct symbol *enum_root;	/* for enum */
struct symbol *s_u_root;	/* for for struct or union */
struct symbol *symbol_root;	/* for symbols */

/*
 * Look up an name in the symbol table.  If "add" is not null, add a
 * the entry to the table.  The table is stored as a splay tree.
 * (Bjorn: This is one of my favourites!)
 */
struct symbol *
findsym(struct symbol **root, char *key, struct symbol *add)
{
	struct symbol *left, *right;
	struct symbol **leftp, **rightp;
	struct symbol *sp1, *sp2, *sp3;
	int cmp;
	int path1, path2;

	if (add) {
		add->key = key;
	}
	sp1 = *root;
	if (sp1 == NULL)
		return add? *root = add : NULL;
	leftp = &left, rightp = &right;
	for (;;) {
		cmp = strcmp( sp1->key, key);
		if (cmp == 0)
			break;
		if (cmp > 0) {
			sp2 = sp1->child[0];
			path1 = 0;
		} else {
			sp2 = sp1->child[1];
			path1 = 1;
		}
		if (sp2 == NULL) {
			if (! add)
				break;
			sp2 = add;
		}
		cmp = strcmp(sp2->key, key);
		if (cmp == 0) {
one_level_only:
			if (path1 == 0) {	/* sp2 is left child of sp1 */
				*rightp = sp1;
				rightp = &sp1->child[0];
			} else {
				*leftp = sp1;
				leftp = &sp1->child[1];
			}
			sp1 = sp2;
			break;
		}
		if (cmp > 0) {
			sp3 = sp2->child[0];
			path2 = 0;
		} else {
			sp3 = sp2->child[1];
			path2 = 1;
		}
		if (sp3 == NULL) {
			if (! add)
				goto one_level_only;
			sp3 = add;
		}
		if (path1 == 0) {
			if (path2 == 0) {
				sp1->child[0] = sp2->child[1];
				sp2->child[1] = sp1;
				*rightp = sp2;
				rightp = &sp2->child[0];
			} else {
				*rightp = sp1;
				rightp = &sp1->child[0];
				*leftp = sp2;
				leftp = &sp2->child[1];
			}
		} else {
			if (path2 == 0) {
				*leftp = sp1;
				leftp = &sp1->child[1];
				*rightp = sp2;
				rightp = &sp2->child[0];
			} else {
				sp1->child[1] = sp2->child[0];
				sp2->child[0] = sp1;
				*leftp = sp2;
				leftp = &sp2->child[1];
			}
		}
		sp1 = sp3;
	}
	/*
	 * Now sp1 points to the result of the search.  If cmp is zero,
	 * we had a match; otherwise not.
	 */
	*leftp = sp1->child[0];
	*rightp = sp1->child[1];
	sp1->child[0] = left;
	sp1->child[1] = right;
	*root = sp1;
	return cmp == 0? sp1 : NULL;
}

/* crc function  slightly modified from gzip */
/*
 * Run a null-terminated string through the crc shift register.
 * If s is a NULL pointer, then initialize the crc shift register
 * contents instead.
 * Return the current crc in either case.
 */

static unsigned long crctab32[] = {
#include "crc32.tab"
};

unsigned long
updcrc32(register unsigned char *s)
{
	static unsigned long crcreg;
	register unsigned long c, *t;

	if (s == 0)
		c = 0xffffffffU;
	else {
		c = crcreg;
		t = crctab32;
		while (*s)
			c = t[((int)c ^ (*s++)) & 0xff] ^ (c >> 8);
	}
	crcreg = c;
	return c ^ 0xffffffffU;
}

void
drop_table(struct symbol *symtab)
{
	if (symtab) {
		if (symtab->child[0])
			drop_table(symtab->child[0]);

		if (symtab->child[1])
			drop_table(symtab->child[1]);

		if (symtab->key)
			free(symtab->key);

		if (symtab->value)
			free(symtab->value);

		free(symtab);
	}
}

void
restart_file(char *id)
{
	char *p;
	char *s;

	if (debug) fprintf(logfile, "RESTART %s\n", id);

	if ((use_globals > 0) && symbol_root)
		show_globals(symbol_root);

	if (use_globals < 0)
		use_globals = 1;

	line_no = 0;

	if (outfile && (outfile != stdout)) {
		if (!dump_defs) {
			fprintf(outfile, "#endif /* %s */\n", macroname);
			fprintf(outfile, "#endif /* CONFIG_MODVERSIONS !__GENKSYMS__ */\n");
		}

		fclose(outfile);
		outfile = (FILE *)0;
	}

	strcpy(infile, strchr(id, '\"'));

	if ((p = strrchr(id, '.')))
		*p = '\0';
	if ((p = strrchr(id, '/')))
		++p;
	else if ((p = strchr(id, '\"')))
		++p;
	else {
		fprintf(stderr, "Illegal filename: %s\n", id);
		p = "_unknown";
	}

	sprintf(outfilename, "%s/%s.ver", outfile_dir, p);

	s = macroname;
	*s++ = '_';
	while (*p) {
		if (*p == '.')
			*s = '_';
		else
			*s = toupper(*p);
		++s; ++p;
	}
	strcpy(s, "_VER_");

	if (outfile == stdout) {
		char **txt = header;

		/* Print the header */
		while (!dump_defs && *txt)
			fprintf(outfile, "%s\n", *txt++);
		fprintf(outfile, "#ifndef %s\n", macroname);
		fprintf(outfile, "#define %s\n", macroname);
	}

	/* Clean the old tables */

	if (type_root) {
		drop_table(type_root);
		type_root = (struct symbol *)0;
	}

	if (enum_root) {
		drop_table(enum_root);
		enum_root = (struct symbol *)0;
	}

	if (s_u_root) {
		drop_table(s_u_root);
		s_u_root = (struct symbol *)0;
	}

	if (symbol_root) {
		drop_table(symbol_root);
		symbol_root = (struct symbol *)0;
	}
}

/*
 * Save string in a safe place for later reference
 */
char *
savestr(char *str, int len)
{
	char *p;

	p = (char *)malloc(len + 1);
	strcpy(p, str);

	return p;
}

/*
 * Centralized error reporting
 */
void
put_err(char *s)
{
	if (warnings)
		fprintf(logfile, "%s %s: input line %d: Note: %s\n",
			progname, infile, line_no, s);
}

/*
 * Centralized error reporting with an extra parameter
 */
void
put_err2(char *s1, char *s2)
{
	if (warnings) {
		fprintf(logfile, "%s %s: input line %d: Note: ",
			progname, infile, line_no);
		fprintf(logfile, s1, s2);
		fprintf(logfile, "\n");
	}
}

/*
 * stuff a string to the end of the work buffer while parsing
 */
void
put(char *s)
{
	strcat(pbuffer, s);
	pbuffer += strlen(s);

	strcat(pbuffer, " ");
	++pbuffer;
	if ((pbuffer - tmpbuffer) >= MAXTMP) {
		fprintf(stderr, "PANIC: buffer overflow! Quitting!\n");
		exit(1);
	}
}

/*
 * Save all symbols (identifiers) found while parsing,
 * so that the symbol table later can be updated with the full definition.
 * We don't know the full definition when we find the symbol while scanning!
 */
char **id_stack;
int id_stack_size;
int id_next;

void
push_id(char *s) /* yytext */
{
	char *key;

	if (debug) fprintf(logfile, "found def <%s>\n", s);
	key = savestr(s, strlen(s));

	if (id_stack) {
		if (id_next >= id_stack_size) {
			id_stack = (char **)realloc(id_stack,
				(id_stack_size + 1) * sizeof(char *));
			id_stack_size += 1;
		}
	}
	else {
		id_stack = (char **)malloc(sizeof(char *));
		id_stack_size = 1;
	}

	id_stack[id_next++] = key;
}

/*
 * Flush the work buffer since we have completed the scan of this
 * definition/declaration.
 * If we have found any identifiers (i.e. this was not just a type declaration)
 * we create entries in the symbol table for these identifiers,
 * with references to this definition.
 */
void
reset_buf(int saveit)
{
	struct symbol *p;
	int i;
	char *value;

	if (debug) fprintf(logfile, "BUFFER: <%s>\n", tmpbuffer);

	if (saveit && id_next) {

		for (i = 0; i < id_next; ++i) {
			if (debug) fprintf(logfile, "DEF <%s>\n", id_stack[i]);
			value = savestr(tmpbuffer, pbuffer - tmpbuffer);
			p = (struct symbol *)calloc(1, sizeof(struct symbol));
			p->value = value;
			findsym(&symbol_root, id_stack[i], p);
		}
	}
	else {
		while (id_next > 0)
			free(id_stack[--id_next]);
	}

	id_next = 0; /* empty stack */
	pbuffer = tmpbuffer;
	*pbuffer = '\0';
}

/*
 * Save a struct/union/enum declaration to its respective symbol table.
 * (Different namespaces for enum vs struct/union)
 * This entry will be used later when expanding a declaration/definition
 * for a "X(symbol)" in the input stream.
 */
struct symbol *
save_tag(int sym, char *tag, char *defstart)
{
	struct symbol *p;
	struct symbol **root;

	if (sym == ENUM)
		root = &enum_root;
	else
		root = &s_u_root;

	p = (struct symbol *)calloc(1, sizeof(struct symbol));
	p->value = savestr(defstart, pbuffer - defstart);
	findsym(root, tag, p);

	if (debug) fprintf(logfile, "S/U/E def <%s> <%s>\n", tag, defstart);

	return p;
}

/*
 * Check the relevant symbol tables for any previous declaration of a
 * struct/union or enum.
 * If we find it, we return 1 else 0.
 * If found: add the definition to the current workbuffer,
 * so that the fully expanded definition of the current symbol will
 * be available when we finally are going looking for it (for the crc)
 *
 * (NOT USED. I left it here for debugging purposes...)
 */
int
expand_tag(int sym, char *tag)
{
	struct symbol *p;
	struct symbol **root;

	if (sym == ENUM)
		root = &enum_root;
	else
		root = &s_u_root;


	if ((p = findsym(root, tag, 0))) {
		put(p->value);
		if (debug) fprintf(logfile, "S/U/E expand <%s>\n", tag);
		return 1;
	}
	else
		return 0;
}

/*
 * Save a typedef definition to its own symbol table.
 * (Different namespaces for typedefs vs enum or struct/union)
 * This entry will be used later when expanding a declaration/definition
 * during the parsing of the input stream.
 */
void
save_type(char *type_tag)
{
	struct symbol *p;
	struct symbol **root = &type_root;

	p = (struct symbol *)calloc(1, sizeof(struct symbol));
	/* subtract 1 to remove trailing space */
	/* it will be added back when expanding */
	--pbuffer;
	*pbuffer = '\0';
	p->value = savestr(tmpbuffer, strlen(tmpbuffer));
	findsym(root, type_tag, p);

	if (debug) fprintf(logfile, "TYPEDEF def <%s> <%s>\n", type_tag, p->value);
}

/*
 * Check the relevant symbol tables for any previous declaration of a
 * typedef with this tag.
 * IF we find it, we immediately add the full definition ('typedef' and all)
 * to the current workbuffer, so that the fully expanded definition of the
 * current symbol will be easily available when we later are going looking
 * for it (for the crc).
 */
int
expand_type(char *type_tag)
{
	struct symbol *p;
	struct symbol **root = &type_root;

	if ((p = findsym(root, type_tag, 0))) {
		put(p->value);
		if (debug) fprintf(logfile, "TYPEDEF expand <%s>\n", type_tag);
		return 1;
	}
	else
		return 0;
}

/*
 * Silly little wrapper for the crc computation.
 * This also prints the (partial) definition if the '-D' option was used
 */
unsigned long
dump_str(char *s)
{
	if (dump_defs)
		printf("%s", s);

	return updcrc32((unsigned char *)s);
}

/*
 * Potentially recursive fucntion for expanding the text of a definition.
 * IF there is a MARK in the string, it signifies that a reference to
 * a struct/union/enum is going on.
 * The tag (and type) of the reference will be grep-ed for and looked
 * up in the relevant symbol table.
 *
 * This will find all the basic types for every declaration/definition
 * in the input stream (you can see the expansion with the '-D' option)...
 *
 * The fully expanded string is put through a 32bit crc computation,
 * so that any changes in any part of the kernel that might "interfere"
 * with the compatibility of the current symbol, will give a different crc!
 */
unsigned long
expand_def(char *value)
{
	struct symbol **root;
	struct symbol *p;
	unsigned long crc = 0;
	char *start = value;
	char *stop;
	char *marked;
	char saved = '\0';

	while ((stop = strchr(start, MARK))) {
		*stop = '\0'; /* null-terminate the partial string */
		crc = dump_str(start); /* the string up to the mark */
		*stop = MARK; /* restore the string */
		start = ++stop;
		/* find type and tag, differ between struct/union vs enum */
		if (strncmp(start, "num", 3) == 0) { /* Yikes, an enum! */
			root = &enum_root;
			marked = "enum";
		}
		else {
			root = &s_u_root;
			if (*start == 't') /* struct */
				marked = "struct";
			else
				marked = "union";
		}

		/* skip over tag after we have gotten hold of it */
		/* skip #truct #nion #num */
		if ((start = strchr(start, ' ')) == (char *)0) {
			put_err("Ouch, lost sync in symbol expansion (1)");
			break;
		}
		/* find start of tag string */
		if (*++start <= ' ') { /* safe guy.. */
			put_err("Ouch, lost sync in symbol expansion (2)");
			break;
		}
		/* find end of tag string */
		if ((stop = strchr(start, ' ')) != (char *)0) {
			saved = *stop;
			*stop = '\0';
		}

		/* start _should_ point to the null-terminated tag */
		/* stop points to the end of the null-terminated tag */
		/* OR stop is NULL for "pre-nulled" string */

		if ((p = findsym(root, start, 0)) == (struct symbol *)0) {
			struct symbol *nodup;
			char *here = pbuffer;
			char *savstart = savestr(start, strlen(start));

			if (warnings)
				fprintf(logfile, "%s %s: warning: symbol [%s]: "
					"unknown '%s %s'\n",
					progname, infile, expand_key, marked, start);
			/* sound action: */
			/* save the offender: no duplicate warnings */
			put(marked);
			put(start);
			put("{ UNKNOWN }");
			if (strcmp(start, "enum") == 0)
				nodup = save_tag(ENUM, savstart, here);
			else
				nodup = save_tag(STRUCT, savstart, here);
			nodup->trail = trail; /* mark it, no loops! */
			trail = nodup;
			crc = dump_str(here); /* the new definition */
			pbuffer = here;
			*pbuffer = '\0';
		}
		else { /* found it */
			if (p->trail) { /* been here before in this expansion */
				dump_str(marked); /* the type */
				dump_str(" ");
				dump_str(start); /* the tag */
				crc = dump_str(" ");
			}
			else { /* Not here before, expand it */
				/* The value contains the type and tag */
				p->trail = trail; /* mark it, no loops! */
				trail = p;
				if (stop)
					*stop = saved; /* restore the string */
				crc = expand_def(p->value);
			}
		}

		/* let us continue with the rest of the string */
		if (stop) {
			*stop = saved; /* restore the string */
			start = ++stop;
		}
		else {
			start = (char *)0; /* flag end of input, for below */
			break;
		}
	}
	/* no more marks have been found (if any!) */

	/*
	 * take care of the proper tail of the input string
	 * (might be the whole string...)
	 */
	if (start && *start)
		crc = dump_str(start);

	return crc;
}

/*
 * Basic main loop for expanding the name of a symbol to its FULL
 * declaration, down to the basic level (like char, int and stuff)...
 */
void
show_def(char *key, char *value)
{
	unsigned long last_crc;
	struct symbol *p;
	struct symbol tail;

	if (debug) fprintf(logfile, "FOUND <%s> = ", key);

	/* reset CRC */
	updcrc32(0);

	if (dump_defs)
		printf("FOUND <%s> = <", key);

	expand_key = key;
	trail = &tail; /* just a marker */
	last_crc = expand_def(value);
	while (trail != &tail) { /* clean up */
		p = trail;
		trail = p->trail;
		p->trail = (struct symbol *)0;
	}

	if (outfile_flag && !outfile) {
		char **txt = header;

		if ((outfile = fopen(outfilename, "w")) == (FILE *)0) {
			perror(outfilename);
			exit(1);
		}

		while (!dump_defs && *txt)
			fprintf(outfile, "%s\n", *txt++);
		fprintf(outfile, "#ifndef %s\n", macroname);
		fprintf(outfile, "#define %s\n", macroname);
	}

	if (!dump_defs)
		fprintf(outfile, "#define %s\t_set_ver(%s, %08lx)\n",
			key, key, last_crc);

	if (dump_defs)
		printf(">\n");
}

/*
 * This function will create a version header based on globals
 * instead of symbols mentioned in an explicit symbol table: X(symbol)
 * It is called at end-of-file IF no symbol table has been found,
 * i.e. from restart_file() or from main() after a finished run.
 */
void
show_globals(struct symbol *symtab)
{
	if (symtab) {
		if (symtab->child[0])
			show_globals(symtab->child[0]);

		if (symtab->child[1])
			show_globals(symtab->child[1]);

		if (symtab->value) {
			if (strncmp(symtab->value, "static ", 7) != 0)
				/* found one! */
				show_def(symtab->key, symtab->value);
		}
	}
}

/*
 * This is the "black hole" for strings and character constants
 */
void
skip_string()
{
	int sym;
	char match;

	match = yytext[0];

	if (debug) fprintf(logfile, "skipping %c string at line %d\n", match, line_no);

	while ((sym = yylex())) {
		switch (sym) {
		case STRING:
			if (yytext[0] == match) {
				if (debug) fprintf(logfile, "done skipping string\n");
				return;
			}

		default:
			if (yytext[0] == '\\')
				yylex(); /* skip escaped */
			if (debug) fprintf(logfile, "line %d: skip sym %d = <%s>\n", line_no, sym, yytext);
			break;
		}
	}

	if (debug) fprintf(logfile, "done skipping string\n");
	return;
}

/*
 * This (recursive) loop will take care of all '{' '}' nested blocks
 * in the input stream that are _not_ enum/struct/union declarations.
 * So, initializers (e.g. for arrays and structs) and function bodies
 * will disappear down into this black hole...
 *
 * Only _special_ identifiers that conform to:  "X(identifier)"
 * will result in any output.
 *
 * This type of strings should only found in sources using them to
 * denote the kernel symbols in a symbol table that is going to be exported.
 *
 * The action will be to generate the "magic" definitions in module
 * include files, e.g.  <linux/module_version.h>
 */
int
skip_block()
{
	struct symbol *p;
	int sym;
	int maybe = 0;

	if (debug) fprintf(logfile, "ENTER SKIP BLOCK\n");

	while ((sym = yylex())) {
		switch (sym) {
		case FILENAME: /* This is an error! */
			put_err("Illegal file start block");
			return 0;

		case RBRACE:
			if (debug) fprintf(logfile, "LEAVE SKIP BLOCK\n");
			return 1;

		case LBRACE:
			maybe = 0;
			if (skip_block() == 0) {
				if (debug) fprintf(logfile, "LEAVE SKIP BLOCK\n");
				return 0;
			}
			break;

		case STRING:
			skip_string();
			break;

		case IDENT:
			if (maybe == 2) {
				/* might be defined even... */
				if ((p = findsym(&symbol_root, yytext, 0))) {
					/* found one! */
					show_def(yytext, p->value);
					/*
					 * If there is a symbol table, we
					 * shouldn't create a header based
					 * on any globals in the source...
					 */
					if (use_globals > 0)
						use_globals = -1;
				}
				else {
					put_err2("Unkown symbol '%s'", yytext);
				}
				maybe = 0;
			}
			else if ((maybe == 0) && (strcmp(yytext, "X") == 0))
				maybe = 1; /* MAYBE an exported symbol... */
			else
				maybe = 0;
			break;

		case LPAREN:
			if (maybe == 1)
				maybe = 2;
			else
				maybe = 0;
			break;

		default:
			maybe = 0;
			break;
		}
	}

	if (debug) fprintf(logfile, "LEAVE SKIP BLOCK\n");
	return 0;
}

/*
 * Parse (maybe recursively) a struct/union/enum definition block.
 * Append it to the work buffer.
 *
 *    This code is in many respects similar to the code in parse(),
 *    but I felt it was easier to maintain one function for the top
 *    level, where all the global definitions are, and another
 *    function that just had to decode a struct/union/enum definition...
 */
void
build_block(void)
{
	int sym;
	int sym1;
	char *tag = (char *)0;
	char *marker;
	char *here;


	while ((sym = yylex())) {
	again:
		switch (sym) {
		case FILENAME: /* This is an error! */
			put_err("Illegal file start build");
			return;

		case TYPEDEF:
			put_err("No typedef allowed here");
			break;

		case STRING:
			skip_string();
			break;

		case RBRACE:
			put("}");
			return;

		case LBRACE:
			put("{");
			build_block();
			break;

		case ENUM:
		case UNION:
		case STRUCT:
			marker = pbuffer; /* current "put" pointer */
			put(yytext);

			if ((sym1 = yylex()) == IDENT) { /* need a tag */
				tag = savestr(yytext, strlen(yytext));
				put(yytext);
				sym1 = yylex();
			}
			else
				tag = (char *)0;

			if (sym1 != LBRACE) { /* then it is a reference */
				if (tag) { /* refer */
					/*
					 * mark for easy retrieval later
					 *
					 * struct -> #truct
					 * union  -> #nion
					 * enum   -> #num
					 */
					*marker = MARK; /* kludge! */
				}
				else
					put_err("Not a legal definition");
				sym = sym1;
				goto again;
			}
			/* else  sym == LBRACE  i.e. a definition! */

			here = pbuffer;

			put("{");
			build_block();
			if (tag) { /* i.e. not anonymous */
				save_tag(sym, tag, marker);

				/* The definition is saved separately,
				 * so the workbuffer will only have to
				 * show an expandable definition
				 */
				pbuffer = here;
				*pbuffer = '\0';
				*marker = MARK;
			}

			break;

		case IDENT:
			if (!expand_type(yytext))
				put(yytext); /* it was not a typedef */
			break;

		case OTHER:
			put(yytext);
			break;

		default:
			put(yytext);
			break;
		}
	}
	
	return;
}

/*
 * Main loop for parsing top level definitions/declarations in the
 * input stream. It has only a rudimentary knowledge of ANSI C,
 * but it _should_ do the right thing...
 *
 * The right thing is hereby defined to be:
 *
 * - Save all typedefs, and look for identifiers that are previously
 * defined as typedefs. Expand these by replacing the identifier
 * with the _full_ typedef definition, and append this to the work buffer.
 *
 * - Save all tagged definitions of struct/union/enum for use in possible
 * expansions at a later time (i.e. when we find the construct: 'X(symbol)'
 * in the input stream). This definition is saved as a separate entity,
 * but is also appended to the current work buffer.
 *
 * - Mark all references to tagged struct/union/enum so that they can
 * easily identified when we later (recursively) expand the definition
 * of a symbol in the input stream (i.e. the 'X(symbol)' as above).
 * The marked reference (marked by changing the first character of
 * the type into '#') is appended to the current work buffer.
 *
 * - Keep track of all global identifiers in the input stream.
 * When a complete definition is parsed, entries will be made in the
 * symbol table, with references to the parsed definition.
 * This definition is created in the work buffer at the time of parsing,
 * and is saved away (malloc) when the full definition has been parsed.
 *
 * - For all other tokens in the input stream: append to the work buffer.
 * Actually only a few tokens are recognized by the lexical analysis,
 * so the OTHER will be handled a charater at a time.
 *
 * NOTE: The work buffer (and thus the internal version of a definition)
 * consist of the tokens, each one followed by a SPACE.
 * The final semicolon in a struct/union/enum definition is not included.
 */
void
parse(void)
{
	int sym;
	int sym1;
	int started = 0; /* have we started a definition/declaration? */
	int type_def = 0; /* is this part of a typedef? */
	int parlev = 0; /* number of unmatched LPARENs, see comment at IDENT! */
	char *marker;
	char *tag;
	char *type_tag = (char *)0;
	char *here;

	while ((sym = yylex())) {
		/* top level */
	top_level:
		switch (sym) {
		case FILENAME: /* This is the start of a new source! */
			restart_file(yytext);
			break;

		case 0:
			return;

		case STRING:
			skip_string();
			break;

		case TYPEDEF:
			if (started || type_def) {
				put_err("No typedef allowed here");
			}
			started = 1;
			type_def = 1;
			type_tag = (char *)0;
			put(yytext);
			break;

		case ENUM:
		case UNION:
		case STRUCT:
			if (started && !type_def && (parlev == 0)) {
				put_err2("'%s' is not allowed here", yytext);
			}
			started = 1;

			marker = pbuffer; /* current "put" pointer */
			put(yytext);

			if ((sym1 = yylex()) == IDENT) { /* tag */
				tag = savestr(yytext, strlen(yytext));
				put(yytext);
				sym1 = yylex();
			}
			else
				tag = (char *)0;


			if (sym1 == LBRACE) { /* define */
				here = pbuffer;

				put("{");
				build_block();
				if (tag) {
					save_tag(sym, tag, marker);
					pbuffer = here;
					*pbuffer = '\0';
					*marker = MARK;
				}

				if ((sym = yylex()) == SEMI) {
					parlev = 0;
					started = 0;
					if (type_def)
						put_err("Illegal typedef");
					type_def = 0;
					if (type_tag)
						free(type_tag);
					/* begin anew in buffer! */
					reset_buf(0);
				}
				else {
					goto top_level;
				}
			}
			else { /* refer */
				if (tag) {
					/*
					 * make a mark for easy
					 * retrieval later...
					 *
					 * struct -> #truct
					 * union  -> #nion
					 * enum   -> #num
					 */
					*marker = MARK; /* kludge! */
				}
				else {
					put_err("Not a legal definition");
				}
				sym = sym1;
				goto top_level;
			}
			break;

		case IDENT: /* must be a TYPE or a typedef at start! */
			if (!expand_type(yytext)) {
				if (!started && !type_def) {
					/* OK, OK... it is an int in disguise */
					/*
					put_err2("type for identifier '%s' is missing", yytext);
					*/
				}
				if (type_def && !type_tag) {
					type_tag = savestr(yytext, strlen(yytext));
				}
				else {
					/* The following, and only real, use
					 * of parlev is here to stop IDENTs in
					 * function parameters from
					 * cluttering up the symbol table.
					 * I take the risk that there aren't
					 * that many "raw" globally defined
					 * pointers to functions, but that
					 * they instead are typedef'ed
					 *
					 * IF this will ever be a problem,
					 * just change the "#if" 1 to "#if 0"...
					 */
#if 0
					if (parlev == 0)
#endif
						push_id(yytext);
				}
				put(yytext);
			}
			started = 1;
			break;

		case LBRACE:
			if (!started) {
				put_err("No '{' allowed here");
			}
			if (skip_block() == 0)
				return;
			/* FALLTHRU */
		case SEMI:
			started = 0;
			if (type_def) {
				type_def = 0;
				if (type_tag)
					save_type(type_tag);
				type_tag = (char *)0;
				reset_buf(0);
			}
			else {
				reset_buf(1); /* save it if it is a def */
			}
			parlev = 0;
			break;

		case LPAREN:
			put("(");
			++parlev;
			break;

		case RPAREN:
			put(")");
			--parlev;
			break;

		case TYPE: /* data types: int ... */
			started = 1;
			/* FALLTHRU */
		case S_TYPE: /* storage types: extern ... */
			put(yytext);
			break;

		default:
		case OTHER:
			if (!started) {
				put_err2("No '%s' allowed here", yytext);
			}
			put(yytext);
			break;
		}
	}
}

int
yywrap()
{
	return 1;
}

int
main(int argc, char **argv)
{
	extern int mkdir(char *, int); /* I'm lazy */
	char *p;

	logfile = stderr;

	if ((p = strrchr(argv[0], '/')))
		progname = p + 1;
	else
		progname = argv[0];

	while ((argc > 1) && (*(p = argv[1]) == '-')) {
		while (*++p) {
			switch (*p) {
			case 'd':
				debug += 1;
				if (debug > 1)
					logfile = stdout;
				break;

			case 'g':
				use_globals = 1;
				break;

			case 'w':
				warnings = 1;
				break;

			case 'q':
				warnings = 0;
				break;

			case 'V':
				printf("%s\n", copyright);
				break;

			case 'D':
				dump_defs = 1;
				break;

			default:
				fprintf(stderr, "usage: %s [-gwqdD] directory\n", progname);
				exit (1);
				break;
			}
		}
		++argv;
		--argc;

	}

	if ((argc > 1) && (argv[1][0])) {
		outfile_dir = argv[1];
		outfile_flag = 1;
		outfile = (FILE *)0;
		++argv;
		--argc;

		for (p = outfile_dir + 1; (p = strchr(p, '/')); p = p+1) {
			*p = '\0';
			if (access(outfile_dir, F_OK) < 0) {
				if (mkdir(outfile_dir, 0755) < 0) {
					perror(outfile_dir);
					exit(1);
				}
			}
			*p = '/';
		}
		if (access(outfile_dir, R_OK | W_OK | F_OK) < 0) {
			if (mkdir(outfile_dir, 0755) < 0) {
				perror(outfile_dir);
				exit(1);
			}
		}
	}
	else
		outfile = stdout;

	parse();

	if ((use_globals > 0) && symbol_root)
		show_globals(symbol_root);

	if (!dump_defs && outfile) {
		fprintf(outfile, "#endif /* %s */\n", macroname);
		fprintf(outfile, "#endif /* CONFIG_MODVERSIONS !__GENKSYMS__ */\n");
	}
	if (outfile != stdout)
		fclose(outfile);

	return 0;
}
