/*
 * $XConsortium: parse.c,v 1.23 92/08/22 14:37:04 rws Exp $
 */
#include "def.h"

extern char	*directives[];
extern struct inclist	maininclist;

find_includes(filep, file, file_red, recursion, failOK)
	struct filepointer	*filep;
	struct inclist		*file, *file_red;
	int			recursion;
	boolean			failOK;
{
	register char	*line;
	register int	type;
	boolean recfailOK;

	while (line = getline(filep)) {
		switch(type = deftype(line, filep, file_red, file, TRUE)) {
		case IF:
		doif:
			type = find_includes(filep, file,
				file_red, recursion+1, failOK);
			while ((type == ELIF) || (type == ELIFFALSE) ||
			       (type == ELIFGUESSFALSE))
				type = gobble(filep, file, file_red);
			if (type == ELSE)
				gobble(filep, file, file_red);
			break;
		case IFFALSE:
		case IFGUESSFALSE:
		    doiffalse:
			if (type == IFGUESSFALSE || type == ELIFGUESSFALSE)
			    recfailOK = TRUE;
			else
			    recfailOK = failOK;
			type = gobble(filep, file, file_red);
			if (type == ELSE)
			    find_includes(filep, file,
					  file_red, recursion+1, recfailOK);
			else
			if (type == ELIF)
			    goto doif;
			else
			if ((type == ELIFFALSE) || (type == ELIFGUESSFALSE))
			    goto doiffalse;
			break;
		case IFDEF:
		case IFNDEF:
			if ((type == IFDEF && isdefined(line, file_red, NULL))
			 || (type == IFNDEF && !isdefined(line, file_red, NULL))) {
				debug(1,(type == IFNDEF ?
				    "line %d: %s !def'd in %s via %s%s\n" : "",
				    filep->f_line, line,
				    file->i_file, file_red->i_file, ": doit"));
				type = find_includes(filep, file,
					file_red, recursion+1, failOK);
				if (type == ELSE)
					gobble(filep, file, file_red);
			}
			else {
				debug(1,(type == IFDEF ?
				    "line %d: %s !def'd in %s via %s%s\n" : "",
				    filep->f_line, line,
				    file->i_file, file_red->i_file, ": gobble"));
				type = gobble(filep, file, file_red);
				if (type == ELSE)
					find_includes(filep, file,
						file_red, recursion+1, failOK);
			}
			break;
		case ELSE:
		case ELIFFALSE:
		case ELIFGUESSFALSE:
		case ELIF:
			if (!recursion)
				gobble(filep, file, file_red);
		case ENDIF:
			if (recursion)
				return(type);
		case DEFINE:
			define(line, file);
			break;
		case UNDEF:
			if (!*line) {
			    warning("%s, line %d: incomplete undef == \"%s\"\n",
				file_red->i_file, filep->f_line, line);
			    break;
			}
			undefine(line, file_red);
			break;
		case INCLUDE:
			add_include(file, file_red, line, FALSE, failOK);
			break;
		case INCLUDEDOT:
			add_include(file, file_red, line, TRUE, failOK);
			break;
		case PRAGMA:
		case ERROR:
		case IDENT:
		case SCCS:
		case EJECT:
			break;
		case -1:
			warning("%s", file_red->i_file);
			if (file_red != file)
			    warning1(" (reading %s)", file->i_file);
			warning1(", line %d: unknown directive == \"%s\"\n",
				 filep->f_line, line);
			break;
		case -2:
			warning("%s", file_red->i_file);
			if (file_red != file)
			    warning1(" (reading %s)", file->i_file);
			warning1(", line %d: incomplete include == \"%s\"\n",
				 filep->f_line, line);
			break;
		}
	}
	return(-1);
}

gobble(filep, file, file_red)
	register struct filepointer *filep;
	struct inclist		*file, *file_red;
{
	register char	*line;
	register int	type;

	while (line = getline(filep)) {
		switch(type = deftype(line, filep, file_red, file, FALSE)) {
		case IF:
		case IFFALSE:
		case IFGUESSFALSE:
		case IFDEF:
		case IFNDEF:
			type = gobble(filep, file, file_red);
			while ((type == ELIF) || (type == ELIFFALSE) ||
			       (type == ELIFGUESSFALSE))
			    type = gobble(filep, file, file_red);
			if (type == ELSE)
			        (void)gobble(filep, file, file_red);
			break;
		case ELSE:
		case ENDIF:
			debug(0,("%s, line %d: #%s\n",
				file->i_file, filep->f_line,
				directives[type]));
			return(type);
		case DEFINE:
		case UNDEF:
		case INCLUDE:
		case INCLUDEDOT:
		case PRAGMA:
		case ERROR:
		case IDENT:
		case SCCS:
		case EJECT:
			break;
		case ELIF:
		case ELIFFALSE:
		case ELIFGUESSFALSE:
			return(type);
		case -1:
			warning("%s, line %d: unknown directive == \"%s\"\n",
				file_red->i_file, filep->f_line, line);
			break;
		}
	}
	return(-1);
}

/*
 * Decide what type of # directive this line is.
 */
int deftype (line, filep, file_red, file, parse_it)
	register char	*line;
	register struct filepointer *filep;
	register struct inclist *file_red, *file;
	int	parse_it;
{
	register char	*p;
	char	*directive, savechar;
	register int	ret;

	/*
	 * Parse the directive...
	 */
	directive=line+1;
	while (*directive == ' ' || *directive == '\t')
		directive++;

	p = directive;
	while (*p >= 'a' && *p <= 'z')
		p++;
	savechar = *p;
	*p = '\0';
	ret = match(directive, directives);
	*p = savechar;

	/* If we don't recognize this compiler directive or we happen to just
	 * be gobbling up text while waiting for an #endif or #elif or #else
	 * in the case of an #elif we must check the zero_value and return an
	 * ELIF or an ELIFFALSE.
	 */

	if (ret == ELIF && !parse_it)
	{
	    while (*p == ' ' || *p == '\t')
		p++;
	    /*
	     * parse an expression.
	     */
	    debug(0,("%s, line %d: #elif %s ",
		   file->i_file, filep->f_line, p));
	    ret = zero_value(p, filep, file_red);
	    if (ret != IF)
	    {
		debug(0,("false...\n"));
		if (ret == IFFALSE)
		    return(ELIFFALSE);
		else
		    return(ELIFGUESSFALSE);
	    }
	    else
	    {
		debug(0,("true...\n"));
		return(ELIF);
	    }
	}

	if (ret < 0 || ! parse_it)
		return(ret);

	/*
	 * now decide how to parse the directive, and do it.
	 */
	while (*p == ' ' || *p == '\t')
		p++;
	switch (ret) {
	case IF:
		/*
		 * parse an expression.
		 */
		debug(0,("%s, line %d: #if %s\n",
			file->i_file, filep->f_line, p));
		ret = zero_value(p, filep, file_red);
		break;
	case IFDEF:
	case IFNDEF:
		debug(0,("%s, line %d: #%s %s\n",
			file->i_file, filep->f_line, directives[ret], p));
	case UNDEF:
		/*
		 * separate the name of a single symbol.
		 */
		while (isalnum(*p) || *p == '_')
			*line++ = *p++;
		*line = '\0';
		break;
	case INCLUDE:
		debug(2,("%s, line %d: #include %s\n",
			file->i_file, filep->f_line, p));

		/* Support ANSI macro substitution */
		{
		    struct symtab *sym = isdefined(p, file_red, NULL);
		    while (sym) {
			p = sym->s_value;
			debug(3,("%s : #includes SYMBOL %s = %s\n",
			       file->i_incstring,
			       sym -> s_name,
			       sym -> s_value));
			/* mark file as having included a 'soft include' */
			file->i_included_sym = TRUE; 
			sym = isdefined(p, file_red, NULL);
		    }
		}

		/*
		 * Separate the name of the include file.
		 */
		while (*p && *p != '"' && *p != '<')
			p++;
		if (! *p)
			return(-2);
		if (*p++ == '"') {
			ret = INCLUDEDOT;
			while (*p && *p != '"')
				*line++ = *p++;
		} else
			while (*p && *p != '>')
				*line++ = *p++;
		*line = '\0';
		break;
	case DEFINE:
		/*
		 * copy the definition back to the beginning of the line.
		 */
		strcpy (line, p);
		break;
	case ELSE:
	case ENDIF:
	case ELIF:
	case PRAGMA:
	case ERROR:
	case IDENT:
	case SCCS:
	case EJECT:
		debug(0,("%s, line %d: #%s\n",
			file->i_file, filep->f_line, directives[ret]));
		/*
		 * nothing to do.
		 */
		break;
	}
	return(ret);
}

struct symtab *isdefined(symbol, file, srcfile)
	register char	*symbol;
	struct inclist	*file;
	struct inclist	**srcfile;
{
	register struct symtab	*val;

	if (val = slookup(symbol, &maininclist)) {
		debug(1,("%s defined on command line\n", symbol));
		if (srcfile != NULL) *srcfile = &maininclist;
		return(val);
	}
	if (val = fdefined(symbol, file, srcfile))
		return(val);
	debug(1,("%s not defined in %s\n", symbol, file->i_file));
	return(NULL);
}

struct symtab *fdefined(symbol, file, srcfile)
	register char	*symbol;
	struct inclist	*file;
	struct inclist	**srcfile;
{
	register struct inclist	**ip;
	register struct symtab	*val;
	register int	i;
	static int	recurse_lvl = 0;

	if (file->i_defchecked)
		return(NULL);
	file->i_defchecked = TRUE;
	if (val = slookup(symbol, file))
		debug(1,("%s defined in %s\n", symbol, file->i_file));
	if (val == NULL && file->i_list)
		{
		for (ip = file->i_list, i=0; i < file->i_listlen; i++, ip++)
			if (val = fdefined(symbol, *ip, srcfile)) {
				debug(1,("%s defined in %s\n",
					symbol, (*ip)->i_file));
				break;
			}
		}
	else if (val != NULL && srcfile != NULL) *srcfile = file;
	recurse_lvl--;
	file->i_defchecked = FALSE;

	return(val);
}

/*
 * Return type based on if the #if expression evaluates to 0
 */
zero_value(exp, filep, file_red)
	register char	*exp;
	register struct filepointer *filep;
	register struct inclist *file_red;
{
	if (cppsetup(exp, filep, file_red))
	    return(IFFALSE);
	else
	    return(IF);
}

define(def, file)
	char	*def;
	struct inclist	*file;
{
    char *val;

    /* Separate symbol name and its value */
    val = def;
    while (isalnum(*val) || *val == '_')
	val++;
    if (*val)
	*val++ = '\0';
    while (*val == ' ' && *val == '\t')
	val++;

    if (!*val)
	val = "1";
    define2(def, val, file);
}

define2(name, val, file)
	char	*name, *val;
	struct inclist	*file;
{
    int first, last, below;
    register struct symtab *sp = NULL, *dest;

    /* Make space if it's needed */
    if (file->i_defs == NULL)
    {
	file->i_defs = (struct symtab *)
			malloc(sizeof (struct symtab) * SYMTABINC);
	file->i_deflen = SYMTABINC;
	file->i_ndefs = 0;
    }
    else if (file->i_ndefs == file->i_deflen)
	file->i_defs = (struct symtab *)
			realloc(file->i_defs,
			    sizeof(struct symtab)*(file->i_deflen+=SYMTABINC));

    if (file->i_defs == NULL)
	fatal("malloc()/realloc() failure in insert_defn()\n");

    below = first = 0;
    last = file->i_ndefs - 1;
    while (last >= first)
    {
	/* Fast inline binary search */
	register char *s1;
	register char *s2;
	register int middle = (first + last) / 2;

	/* Fast inline strchr() */
	s1 = name;
	s2 = file->i_defs[middle].s_name;
	while (*s1++ == *s2++)
	    if (s2[-1] == '\0') break;

	/* If exact match, set sp and break */
	if (*--s1 == *--s2) 
	{
	    sp = file->i_defs + middle;
	    break;
	}

	/* If name > i_defs[middle] ... */
	if (*s1 > *s2) 
	{
	    below = first;
	    first = middle + 1;
	}
	/* else ... */
	else
	{
	    below = last = middle - 1;
	}
    }

    /* Search is done.  If we found an exact match to the symbol name,
       just replace its s_value */
    if (sp != NULL)
    {
	free(sp->s_value);
	sp->s_value = copy(val);
	return;
    }

    sp = file->i_defs + file->i_ndefs++;
    dest = file->i_defs + below + 1;
    while (sp > dest)
    {
	*sp = sp[-1];
	sp--;
    }
    sp->s_name = copy(name);
    sp->s_value = copy(val);
}

struct symtab *slookup(symbol, file)
	register char	*symbol;
	register struct inclist	*file;
{
	register int first = 0;
	register int last = file->i_ndefs - 1;

	if (file) while (last >= first)
	{
	    /* Fast inline binary search */
	    register char *s1;
	    register char *s2;
	    register int middle = (first + last) / 2;

	    /* Fast inline strchr() */
	    s1 = symbol;
	    s2 = file->i_defs[middle].s_name;
	    while (*s1++ == *s2++)
	        if (s2[-1] == '\0') break;

	    /* If exact match, we're done */
	    if (*--s1 == *--s2) 
	    {
	        return file->i_defs + middle;
	    }

	    /* If symbol > i_defs[middle] ... */
	    if (*s1 > *s2) 
	    {
	        first = middle + 1;
	    }
	    /* else ... */
	    else
	    {
	        last = middle - 1;
	    }
	}
	return(NULL);
}

undefine(symbol, file)
	char	*symbol;
	register struct inclist	*file;
{
	register struct symtab *ptr;
	struct inclist *srcfile;
	while ((ptr = isdefined(symbol, file, &srcfile)) != NULL)
	{
	    srcfile->i_ndefs--;
	    for (; ptr < srcfile->i_defs + srcfile->i_ndefs; ptr++)
		*ptr = ptr[1];
	}
}
