/*	parse.c
 *
 *  $Log: parse.c,v $
 *  Revision 1.2  1997/07/03 19:18:48  warwick
 *  Changed fixed-length vectors to realloc()ed thingies.
 *
 * Licensed under the GPL. <www.gnu.org>
 *
 *  Revision 1.1  1997/07/03 18:29:57  warwick
 *  Initial revision
 *
 */

#include <strings.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>

#include "truefalse.h"

#include "parse.h"
#include "tokens.h"
#include "skiplist.h"

#if 0
#	define DEBUG(a) { a; }
#	define DLINE { printf("%d\n",__LINE__); }
#else
#	define DEBUG(a) { ; }
#	define DLINE
#endif

// #define FORMAT

struct id {
	char *name;
	char *value;
	int	  evaluated;
};

void evaluate( struct id *this, SKIPHEADER *ids );

/* --------------------------------------------------------------------- */
/* new_id creates or reuses an entry in the ids list */

struct id *new_id( char *tok, SKIPHEADER *ids ) {
	struct id *thisid=malloc(sizeof(struct id));
	struct id *thatid=NULL;
	thisid->name=strdup(tok);
	thisid->value=NULL;
	thisid->evaluated=FALSE;
	thatid=skip_add(thisid,ids);
	if ( thisid != thatid ) {
		free(thisid->name); free(thisid);
		free(thatid->value); thatid->value=NULL;
		if ( thatid->evaluated ) {
			printf("Attempt to reassign %s after evaluation\n",thatid->name);
			exit(0);
		}
		thatid->evaluated=FALSE;
	}
	return thatid;
}

void print_ids ( SKIPHEADER *ids )
{
	struct id **this=(struct id**)skip_first(ids);
	printf("Start\n");
	for ( ; this ; this=(struct id**)skip_next((void**)this) ) {
//		printf("%p %p ",this,skip_next((void**)this)); fflush(stdout);
//		printf("%p = %p\n",
//			   (*this)->name,
//			   (*this)->value); fflush(stdout);
		printf("(%d) %s = %s\n",
			   (*this)->evaluated,
			   (*this)->name,
			   (*this)->value); fflush(stdout);
	}
	printf("Fin\n");
}
/* --------------------------------------------------------------------- */
/* The comparator for the hash */

static int id_cmp ( void *a, void *b ) {
	return strcmp(((struct id*)a)->name,((struct id*)b)->name);
}

/* --------------------------------------------------------------------- */
/* Activates defaults when a match occurs */

void defaults_into_ids( char ***defaults, int depth, SKIPHEADER *ids) {
	int deep=0;
	for ( deep=0; deep<=depth; deep++) {
		char **p=defaults[deep];
		for ( ; *p; p+=2 ) {
			struct id *thisid;
DEBUG(printf("Activating < %s = %s >\n",*p,*(p+1)));
			thisid=new_id(*p,ids); free(*p); *p=0;
			thisid->value=*(p+1); *(p+1)=0;
		}
	}
}

/* --------------------------------------------------------------------- */
/* Copies guards into defaults */

int copy_guards_to_defaults(
						char **groups,
						char ***guards,
						char ***defaults,
						int thisdefault,
						int depth) {
DEBUG(printf("\nFake guards? %d %p\n", depth-1,*guards[depth-1]));
	if ( *guards[depth-1] ) {
		int ix;
		char **p=guards[depth-1];
		char *var=malloc(strlen(groups[depth-2])+2);
		strcpy(var+1,groups[depth-2]);
		*var=' ';
DEBUG(printf("\nFake guards.. %d %p\n",ix,*p));
		for ( ix=1 ; *p && ix<10 ; p++, ix++ ) {
DEBUG(printf("\nTest.. %d %p\n",ix,*p));
			var[1]='0'+ix;
			defaults[depth]=realloc(defaults[depth],(thisdefault*2+1)*sizeof(char*));
			defaults[depth][thisdefault*2-2]=strdup(var);
			defaults[depth][thisdefault*2-1]=strdup(*p);
			defaults[depth][thisdefault*2]=0;
DEBUG(printf(" GUARD %s = %s\n",var,*p));
			thisdefault++;
		}
	}
	return thisdefault;
}

/* --------------------------------------------------------------------- */
/* Frees defaults for the given depth */

void free_defaults_and_guards( char ***defaults, char ***guards, int depth ) {
	char **p=defaults[depth];
	for ( ; *p; p+=2 ) {
DEBUG(
		printf("\nFreeing default <%s = %s>\n",*p,*(p+1));
);
		free(*p); free(*(p+1));
	}
	free(defaults[depth]);
	p=guards[depth];
	for ( ; *p; p++ ) {
DEBUG(
		printf("\nFreeing guard [%s]\n",*p);
);
		free(*p);
	}
	free(guards[depth]);
}

/* --------------------------------------------------------------------- */

extern void parse( FILE *IN ) {
	char *tok=NULL;					// The current token
	
	int depth=0,					// Depth of grouping. 0=file-level
		matchdepth=1,				// Matched-okay level
			// Matches and match-propogation only occur when this is >=depth
			// Settings only occur when this is >depth
			// This := depth+1 when a guard matches or for an unguarded grouper
			// This := depth with a non-expression or a new guardblock.

			//  `guard-blocks' - guards connected with a ,
		hasguards=1,		// Count of guards in guardblock.
							//	Starts 1 to fake a default.
		thisdefault=0;		// Current `default slot' pointer and defaultblock
							//	flag. Starts 0 because depth=0 `defaults'
							//	are currently used to seed the ids.

	int	wascomma=0;			// Last token was a comma
		
	char ***defaults;		// [0..depth][0..&NULL]strdup(char*) {id,val}

	char ***guards;			// [0..depth][0..&NULL]strdup(char*) {val}
	
	char **groups	=NULL;	// [0..depth]strdup(char*) {id}
	char **tomatch	=NULL;	// Values guards at each level must match,
							// or NULL for no such value.

	enum Context context=CON_GUARD;	// Context of parse.

	SKIPHEADER *ids=new_skip_head( id_cmp, 4 );	// Gathers matched stuff

	struct id *thisid=NULL;			// Hacky level0 init stuff (should be ENV)
	
	defaults	=malloc((depth+1)*sizeof(char**));
	*defaults	=malloc(sizeof(char*));
	**defaults	=NULL;
	// Defaults are kept one high, in case the hacky thisid goes away.
	
	guards		=malloc((depth+1)*sizeof(char**));
	*guards		=malloc(sizeof(char*));
	**guards	=NULL;
	// Guards are kept one-too-high, since they have to be collected, in case.

	for ( ; (tok=nexttoken(tok,context,IN)) ; wascomma = (tok && *tok==',') ) {
		switch ( context ) {
		case CON_GUARD:

// GROUP START OR END
			
			if ( *tok==':' ) {
				if ( wascomma ) {
					fprintf(stderr,"Comma preceeded group.\n");
					exit(0);
				}
				tok=nexttoken(tok,context,IN);
				if ( *tok==' ' ) {		// Start of group.
					
// GROUP START

					char *group=strdup(tok);
					char *match=NULL;
DEBUG(
printf("\n1 W%d H%d M%d D%d\n", wascomma,hasguards,matchdepth,depth);
);

/// Propogate match/nomatch

					if ( matchdepth >= depth && !hasguards ) matchdepth=depth+1;
					if ( matchdepth > depth ) {
						struct id *id=skip_find(&group,ids);
						match=id?strdup(id->value):NULL;	// value of GROUPname
					}

/// Check syntax
					
					tok=nexttoken(tok,context,IN);
					if ( *tok!=':' ) {
						printf("\nUnexpected token %s\n",tok);
						exit(0);
					}
#ifdef FORMAT
					printf("\n%*s%s%s",depth,"",":",group);
#endif
DEBUG(
					if ( match ) printf("=%s",match);
);

/// Push guards/defaults/groups/tomatch contexts onto their stacks.

					depth+=1; thisdefault=1; hasguards=1;
					if ( matchdepth == depth ) matchdepth=depth+1;// for default
					
					guards	= realloc(guards,	sizeof(char**)*(depth+1));
					defaults= realloc(defaults,	sizeof(char**)*(depth+1));
					groups	= realloc(groups,	sizeof(char*)*(depth));
					tomatch	= realloc(tomatch,	sizeof(char*)*(depth));

					defaults[depth]=calloc(1,sizeof(char*));
					guards  [depth]=calloc(1,sizeof(char*));
					groups	[depth-1]=group;
					tomatch	[depth-1]=match;

/// Fake guards for parent group in as DEFAULTS for this group.
					if ( depth>1 && matchdepth>=depth ) {
						    thisdefault=copy_guards_to_defaults(groups,guards, defaults, thisdefault, depth);
					}
				}
				else if ( *tok==':' ) {		// End of group

// GUARD END
					
					tok=nexttoken(tok,context,IN);
#ifdef FORMAT
					printf("\n%*s%s%s",depth-1,"","::",tok);
#endif
					if ( *tok!=' ' ) {
						printf("\nUnexpected token %s\n",tok);
						exit(0);
					}
					if ( strcmp(tok,groups[depth-1]) ) {
						printf("\nExpected ::%s, found ::%s\n",
							   	groups[depth-1],tok);
						exit(0);
					}

/// POP the guards groups tomatch defaults context stacks
					
					free(groups [depth-1]);
					free(tomatch[depth-1]);
					free_defaults_and_guards( defaults, guards, depth );

/// Propogate matching and depth
					
					depth-=1; thisdefault=hasguards=0;
					if ( matchdepth>depth ) matchdepth=depth;
				}
			}

// Assignment
			
			else if ( *tok=='=' ) {
				if ( wascomma ) {
					fprintf(stderr,"Comma preceeded equals.\n");
					exit(0);
				}
#ifdef FORMAT
				printf(" = ");
#endif
				context=CON_EXPRESSION;
			}

// Guard or start of assignment
			
			else if ( *tok==' ' || *tok=='"' || *tok=='$' ) {
DEBUG(
printf("\n2 W%d H%d M%d D%d\n", wascomma,hasguards,matchdepth,depth);
);

/// Start of assignment

				if ( *tok==' ' && *seetoken(context,IN)=='=' ) {
					if ( wascomma ) {
						printf("Comma preceeded expression\n");
						exit(0);
					}
					if ( !hasguards ) {
						printf("\nNon-default expression lacked guards\n");
						exit(0);
					}
#ifdef FORMAT
					printf("\n%*s",depth+2,"");
#endif

//// Assignment is a default..
					
					if ( matchdepth>depth ) {
						if ( thisdefault ) {
							defaults[depth]=realloc(defaults[depth],(thisdefault*2+1)*sizeof(char*));
							defaults[depth][thisdefault*2-2]=strdup(tok);
							defaults[depth][thisdefault*2-1]=0;
						}
						else {

//// Assignment is not a default. Copy defaults to ids in preparation.
							
							/* Copy all defaults to new_id */
							defaults_into_ids(defaults, depth, ids);

//// Prepare for second half of assignment
							
DEBUG(
							printf("(newid)");
);
							thisid=new_id(tok, ids);
						}
					}
#ifdef FORMAT
					printf("%s",tok);
#endif
				}
				else if ( matchdepth>=depth ) {


/// Is a guard. 
//// Not a guardlist - break from previous guards, kill guardlist.
					
					if ( !wascomma ) {
						matchdepth=depth;
						while ( hasguards>0 )
							free(guards[depth][--hasguards]);
					}
//// Record this guard in the guardlist
					thisdefault=0; hasguards++;
					guards[depth]=realloc(guards[depth],(hasguards+1)*sizeof(char*));
					guards[depth][hasguards]=NULL;
					guards[depth][hasguards-1]=strdup(tok);
					if ( !tomatch[depth-1] ) {
						printf("Guard (%s) without match\n",tok);
						exit(0);
					}

//// Test for match
					
#ifdef FORMAT
					printf("\n%*s%s",depth,"",tok);
#endif
					if ( strcmp(tomatch[depth-1],tok) ) {
DEBUG(
						 printf(" (nomatch)");
);
					}
					else {
DEBUG(
						 printf(" (match %d)",depth);
);
						 matchdepth=depth+1;
					}
				}
				else {
#ifdef FORMAT
					printf("\n%*s%s",depth,"",tok);
#endif
				}
			}

// Just a comma
		
			else if ( *tok==',' ) {
#ifdef FORMAT
				printf(" ,");
#endif
			}
			else {
				printf("UNKNOWN: %s\n",tok);
				exit(0);
			}
			break;
		case CON_EXPRESSION:

// Second half of assign
			
#ifdef FORMAT
			printf("%s%c",tok,*tok);
#endif
			if ( matchdepth>depth ) {
				if ( thisdefault ) {
					defaults[depth][thisdefault*2-1]=strdup(tok);
					defaults[depth][thisdefault*2]=0;
					thisdefault++;
				}
				else {
					thisid->value=strdup(tok);
				}
			}
			context=CON_GUARD;
			break;
		}
	}
	{
		struct id **this=(struct id**)skip_first(ids);
		printf("\nResults\n");
		for ( ; this ; this=(struct id**)skip_next((void**)this) ) {
			if ( isdigit((*this)->name[1]) ) continue;
			if ( *(*this)->value=='$' ) evaluate(*this, ids);
			printf("(%d) %s = %s\n",
				   (*this)->evaluated,
				   (*this)->name,
				   (*this)->value);
		}
		printf("Done\n");
	}
}

void evaluate( struct id *this, SKIPHEADER *ids ) {
DEBUG(
printf("evaluate %s %p\n",this->name,ids);
);
	if ( this->evaluated ) {
		printf("Attempt to re-evaluate %s (%s).%s\n",
			   this->name,this->value,
			   this->evaluated==2?" (Loop detected)":"");
		exit(0);
	}
	this->evaluated=2;	/* Loop detection. */
	
	{
		int ix, thislen=strlen(this->value), varlen, vallen;
		char term, *value;
		for ( ix=1; this->value[ix]; ix++ ) {
			struct id *val;
			if ( this->value[ix] != '$' ) continue;
			this->value[ix]=' ';
			for ( varlen=1;
				  isalnum(this->value[ix+varlen]) ||
				 	 this->value[ix+varlen]=='_';
				  varlen++)
				;
DEBUG( printf("Var %d %s\n",varlen,this->value+ix) );
			{
				char *varname=this->value+ix;
				term=varname[varlen]; varname[varlen]=0;
				val=skip_find(&varname,ids);
				if ( !val ) {
					printf("Unknown variable: %s\n",varname);
					exit(0);
				}
				if ( !val->value || !*val->value ) {
					printf("Variable %s was NULL or NUL\n",varname);
					exit(0);
				}
				varname[varlen]=term;
				if ( *val->value=='$' ) evaluate(val,ids);
				value=val->value;
			}
			vallen=strlen(value+1);	/* Values have " prepended */
			thislen-=varlen-vallen;
			if ( varlen > vallen ) {
				strncpy(this->value+ix,value+1,vallen);
				memmove(this->value+ix+vallen,
						this->value+ix+varlen,
						thislen+1-ix-vallen);
				this->value=realloc(this->value,thislen+1);
				ix+=vallen-1;
			}
			else {
				this->value=realloc(this->value,thislen+1);
				memmove(this->value+ix+vallen,
						this->value+ix+varlen,
						thislen+1-ix-vallen);
				strncpy(this->value+ix,value+1,vallen);
				ix+=vallen-1;
			}
DEBUG(
printf("All %s\n",this->value);
printf("Rest %s\n",this->value+ix);
);
		}
	}
	
	*(this->value)='"';
	this->evaluated=1;	/* Successful departure */
}
