/*
 *				b u i l d 5 . c
 *
 * Subroutines
 *
 * Symbol table management
 *
 * sylookup	finds a symbol, may create new one
 * syvalue	finds a symbol, returns value (maybe returns NULL)
 * syset	sets a value in a symbol
 * sysave	saves a value in free storage and syset's it.
 * syperm	makes values permanent (after reading model files)
 * syclean	removes non-permanent symbols
 * sydump	dump symbol table -- debug
 * sydodump	dump one symbol -- debug
 *
 */

#include	<stdio.h>
#include	"build.h"

static SYMBOL	*sy_first = NULL;		/* Symbol table chain	*/

SYMBOL *
sylookup(name, create)
char		*name;		/* Symbol name string			*/
int		create;		/* True if creation ok			*/
/*
 * Find (or create) a symbol, returning a pointer to it.  If create
 * is FALSE and the symbol is not present, NULL is returned and
 * an error message written.
 */
{
	register SYMBOL	*sy;

	for (sy = sy_first; sy != NULL; sy = sy->sy_next) {
		if (streq(name, sy->sy_name))
			return (sy);
	}
	if (!create) {
		fprintf(stderr, "?No definition for symbol \"%s\"\n", name);
	}
	else {
		/*
		 * Allocate a new symbol
		 */
		sy = myalloc(sizeof (SYMBOL));
		sy->sy_next = sy_first;
		sy_first = sy;
		sy->sy_name = savest(name);
		/*
		 * Note -- other fields were null'ed by myalloc.	
		 */
	}
	return (sy);
}

char *
syvalue(name)
char		*name;
/*
 * Return value associated with name
 */
{
	register SYMBOL	*sy;

	sy = sylookup(name, FALSE);
	return ((sy == NULL) ? NULL : sy->sy_value);
}

SYMBOL *
sysave(name, value, flag)
char		*name;
char		*value;
int		flag;
/*
 * after saving the value string in free storage, call syset to
 * put it in the symbol table.
 */
{
	return(syset(name, savest(value), flag));
}

SYMBOL *
syset(name, value, flag)
char		*name;
char		*value;
int		flag;
/*
 * Give symbol this value -- the value is not savest'd.
 * flag has the bits to set in sy_flag.
 * If the value is to be savest'd, proceed as follows:
 *	syset(name, savest(value), flag);
 * In general, flag should equal 0 or MODEL.  The FREE bit
 * is set by syset().  It must be cleared (by calling syperm() or
 * explicitly if the value must not be free'd.
 */
{
	register SYMBOL	*sy;

	sy = sylookup(name, TRUE);
	if (sy->sy_value != NULL && (sy->sy_flag & FREE))
		myfree(sy->sy_value);
	if ((sy->sy_flag & USED) && !streq(name, "*") && !streq(name, "?")) {
		fprintf(stderr,
			"warning, %s changed from to \"%s\" after use\n",
			name, sy->sy_value, value);
	}
	sy->sy_value = value;
	sy->sy_flag |= (flag | FREE);	/* Set flag bits		*/
	sy->sy_flag &= ~DEFAULT;	/* Not default value now	*/
	return (sy);
}

syperm()
/*
 * Make current symbol table permanent.   This is called 
 * after reading all model files.
 */
{
	register SYMBOL	*sy;

	for (sy = sy_first; sy != NULL; sy = sy->sy_next) {
		sy->sy_perm = sy->sy_value;
		sy->sy_flag &= ~FREE;
	}
}

syclean()
/*
 * Recover storage by deleting all non-permanent symbol values
 * Values are then reset to their defaults.
 */
{
	register SYMBOL *sy;

	for (sy = sy_first; sy != NULL; sy = sy->sy_next) {
		if (sy->sy_value != NULL && (sy->sy_flag & FREE))
			myfree(sy->sy_value);
		sy->sy_value = sy->sy_perm;
		sy->sy_flag &= ~(FREE | USED);
		sy->sy_flag |= DEFAULT;
	}
}

sydump()
/*
 * Dump symbol table
 */
{
	register SYMBOL *sy;

	printf("\n** ** symbol table dump");
#ifdef	decus
	calltr(stdout);
#endif
	for (sy = sy_first; sy != NULL; sy = sy->sy_next) {
		sydodump(sy->sy_name);
	}
}

sydodump(name)
char		*name;
/*
 * Dump one symbol, given its name
 */
{
	register SYMBOL	*sy;
	register char	*value;
	register int	flag;
	char		*perm;

	if ((sy = sylookup(name, FALSE)) == NULL)
		printf("no definition of \"%s\"\n");
	else {
		value = sy->sy_value;
		perm  = sy->sy_perm;
		flag  = sy->sy_flag;
		printf("$(%s %s%s%s%s%s) = {%s}",
			sy->sy_name,
			(value == perm)  ? "P" : "",
			(flag & MODEL)   ? "M" : "",
			(flag & FREE)    ? "F" : "",
			(flag & USED)    ? "U" : "",
			(flag & DEFAULT) ? "D" : "",
			(value != NULL)  ? value : perm);
		if (value != perm)
			printf(", {%s}", perm);
		printf("\n");
	}
}

int
isdefault(name)
char		*name;
/*
 * True if name has not been changed by a model or file string.
 */
{
	register SYMBOL	*sy;

	sy = sylookup(name, FALSE);
	if (sy == NULL)
		return (TRUE);
	if ((sy->sy_flag & DEFAULT) != 0)
		return (TRUE);
	return (FALSE);
}

/*
 * File name management
 */

setfile(name, filetype)
char		*name;			/* File name			*/
char		*filetype;		/* If none specified		*/
/*
 * Save a file name string:
 *	$?		= filename.ext
 *	$*		= filename (no .ext)
 *
 * To save the currently open file, do
 *
 *	setfile(fgetname(fd, work), NULL);
 */
{
	register char	*np;
	register int	c;

	/*
	 * Skip over node::dev:[account]
	 */
	for (np = &name[strlen(name)]; np > name && np[-1] != ':'; np--)
		;
	/*
	 * Skip over [account]
	 */
	if (*np == '[' || *np == '(') {
		while ((c = *np++) != EOS && c != ']' && c != ')')
			;
		if (c == EOS) {
			fprintf(stderr, "bug, bad file name: \"%s\"\n", name);
			np--;
		}
	}
	setstrip(np, ';', "?", filetype);
	setstrip(np, '.', "*", NULL);
}

setstrip(np, strip, key, filetype)
register char	*np;		/* Text					*/
char		strip;		/* Byte to strip			*/
char		*key;		/* Where to store it			*/
char		*filetype;	/* NULL if none, else ".foo"		*/
/*
 * Remove what's after strip and store under key.
 */
{
	register int	c;
	register char	*newp;
	int		dotseen;

	newp = savest(np);
	dotseen = (filetype == NULL);
	for (np = newp; (c = toupper(*np)) != EOS && c != strip;) {
		*np++ = c;
		if (c == '.')
			dotseen = TRUE;
	}
	*np = EOS;
	np = myrealloc(newp, np-newp+1);	/* Shorten it		*/
	if (!dotseen) {
		np = csavest(np, filetype);	/* maybe lengthen it	*/
	}
	syset(key, np, 0);
}

/*
 * Storage allocation functions
 */

#ifndef	vms
char		*my_free = NULL;	/* Needed by myrealloc(), this	*/
					/* must be set to malloc(1) on	*/
					/* system startup.		*/
#endif

#ifdef	CHECKALLOC
unsigned int	curallo = 0;		/* Bytes allocated		*/
unsigned int	hwmallo = 0;		/* Allocatin high water mark	*/
#endif

char *
myalloc(size)
int		size;			/* Number of bytes to get	*/
/*
 * Allocate an area size bytes long.  Return with the area set to 0.
 * If no memory, the program aborts.
 */
{
	register char	*result;

	if ((result = calloc(size, sizeof (char))) == NULL)
		fatal("No memory", NULL);
#ifdef	CHECKALLOC
	curallo += (((unsigned int *) result)[-1]) - ((unsigned int)result);
	if (curallo > hwmallo)
		hwmallo = curallo;
#endif
	return (result);
}

#ifdef	CHECKALLOC
myfree(old)
char		*old;
/*
 * Free a buffer, update curallo
 */
{
	curallo -= (((unsigned int *) old)[-1]) - ((unsigned int)old);
	free(old);
}
#endif

char *
myrealloc(old, size)
char		*old;			/* Old area			*/
int		size;			/* Number of bytes to get	*/
/*
 * Make the old allocated area size bytes long.
 * If no memory, the program aborts.
 */
{
	register char	*result;

	myfree(old);			/* Crashes if old == NULL	*/
#ifndef	vms
	free(my_free);			/* Crashes if my_free == NULL	*/
	my_free = malloc(1);		/* Reset the free pointer	*/
#endif
	if ((result = realloc(old, size)) == NULL)
		fatal("No memory", NULL);
#ifdef	CHECKALLOC
	curallo += (((unsigned int *) result)[-1]) - ((unsigned int)result);
	if (curallo > hwmallo)
		hwmallo = curallo;
#endif
	return (result);
}

/*
 * Store strings in free memory
 */

char *
savest(string)
char		*string;		/* What to store		*/
/*
 * Save this string
 */
{
	register char	*result;

	result = myalloc(strlen(string) + 1);
	strcpy(result, string);
	return(result);
}

char *
csavest(old, new)
char		*old;			/* Append new string onto old	*/
char		*new;			/* New stuff			*/
/*
 * Concatenate new onto old.  Old was allocated using savest().
 */
{
	register char	*result;

	result = myrealloc(old, strlen(old) + strlen(new) + 1);
	strcat(result, new);
	return(result);
}

char *
skipwhite(string)
register char	*string;		/* String pointer		*/
/*
 * Skip over whitespace (space, tab, or newline).
 * Return pointer to first non-whitespace char or pointer to EOS.
 */
{
	while (*string != EOS && iswhite(*string))
		string++;
	return (string);
}

int
iswhite(byte)
int		byte;
/*
 * TRUE if byte is whitespace (FALSE if byte is EOS).
 */
{
	return (byte == ' ' || byte == '\t' || byte == '\n');
}

int
mmatch(string, pattern, minimum)
register char	*string;		/* What to look for 		*/
char		*pattern;		/* Must be in lower case	*/
int		minimum;		/* Must match this many bytes	*/
/*
 * Match string against pattern.
 * mmatch() returns TRUE if string == pattern or at least minimum bytes
 * match.
 */
{
	register char	*patt;
	register char	*c;

	patt = pattern;
	while ((c = *string) != EOS && c == *patt) {
		patt++;
		string++;
	}
	return (*patt == EOS || (patt - pattern) >= minimum);
}


/*
 * File open and error message functions
 */

FILE *
myfopen(name, mode)
char		*name;
char		*mode;
/*
 * Fopen with error message
 */
{
	register FILE	*fd;

	if (name == NULL)
		return (NULL);
	if ((fd = fopen(name, mode)) == NULL) {
		perror(name);
	}
	return (fd);
}

char *
getline(fd)
FILE		*fd;
/*
 * Read a line to inline.
 */
{
	return (fgets(inline, sizeof inline, fd));
}

FILE *
out(byte, fd)
char		byte;
FILE		*fd;
{
	if (byte != EOS)
		putc(byte, fd);
	return (fd);
}


fatal(message, arg)
char		*message;
char		*arg;
/*
 * Abort
 */
{
	if (arg != NULL)
		perror(arg);
	error(message);
}
