/* Copyright (C) 2001 Michael Leonhard
 * Mike Leonhard
 * mike at tamale dot net
 * http://tamale.net/
 */
#include <assert.h>
#include <stdlib.h>
#include "ascorbic.h"

int Assembly_Value( struct Ascorbic *asc, int type, struct Particle *particle );

void Assembly_PurgeLocalScope( struct Ascorbic *asc ) {
	struct Symbol *symbol;
	int s;
	
	assert( asc );
	assert( asc->out );
	assert( asc->symbols );
	assert( asc->symbols->num > -1 );
	assert( asc->symbols->space >= asc->symbols->num );
	assert( asc->symbols->list );
	
	/* start with youngest symbol */
	s = asc->symbols->num - 1;
	
	/* walk through symbols till sub symbol is reached */
	while( asc->symbols->list[s]->type != subtype ) {
		assert( asc->symbols->list[s]->type > -1 );
		assert( asc->symbols->list[s]->name );
		assert( asc->symbols->list[s]->place > -1 );
		assert( asc->symbols->list[s]->produce == 0 );
		assert( asc->symbols->list[s]->consume == 0 );
		
		/* pop variable from frame /
		printf( "var %s at %d, frameheight %d\n", asc->symbols->list[s]->name, asc->symbols->list[s]->place, asc->frameheight );/**/
		assert( asc->symbols->list[s]->place == asc->frameheight - 1 );
		fprintf( asc->out, " POP;\t\t//discard %s\n", asc->symbols->list[s]->name );
		asc->frameheight--;
		
		/* free symbol */
		free( asc->symbols->list[s] );
		asc->symbols->list[s] = NULL;
		
		/* next older symbol */
		s--;
		assert( s > -1 );
		}
	
	/* all younger symbols have been freed */
	asc->symbols->num = s + 1;
	}

struct Symbol *Assembly_FindSymbol( struct Ascorbic *asc, char *name ) {
	struct Symbol *symbol;
	int s;
	
	assert( asc );
	assert( name );
	
	/* symbol list */
	assert( asc->symbols );
	assert( asc->symbols->num > -1 );
	assert( asc->symbols->space > -1 );
	assert( asc->symbols->num <= asc->symbols->space );

	/* each symbol */
	/* TODO: enhance search performance by adding string hashes */
	for( s = 0; s < asc->symbols->num; s++ ) {
		symbol = asc->symbols->list[s];
		assert( symbol );
		assert( symbol->name );
		if( strcmp( symbol->name, name ) == 0 ) return symbol;
		}
	
	/* symbol not found */
	return NULL;
	}

int Assembly_AddSymbol( struct Ascorbic *asc, int type, char *name, int place, int produce, int consume ) {
	struct SymbolList *list;
	struct Symbol *symbol;
	
	assert( asc );
	assert( type == subtype || type == inttype || type == floattype || type == stringtype );
	assert( name );
	
	/* search for symbol with name */
	symbol = Assembly_FindSymbol( asc, name );
	
	/* symbol with same name exists */
	if( symbol != NULL ) return -1;
	
	/* create symbol */
	symbol = (struct Symbol *)malloc( sizeof( struct Symbol ) );
	assert( symbol );
	symbol->type = type;
	symbol->name = name;
	symbol->place = place;
	symbol->produce = produce;
	symbol->consume = consume;
	/*printf( "symbol %s at %d\n", name, place );/**/
	
	/* symbol list */
	list = asc->symbols;
	assert( list );
	assert( list->num > -1 );
	assert( list->space > -1 );
	assert( list->num <= list->space );

	/* need more space */
	if( list->num == list->space ) {
		/* more space */
		list->space *= 2;
		if( list->space == 0 ) list->space = 32;
		list->list = (struct Symbol **)realloc( list->list, sizeof( struct Symbol *) * list->space );
		assert( list->list );
		}
	
	/* add the symbol */
	list->list[list->num] = symbol;
	list->num++;
	
	return 1;
	}

int Assembly_Integer( struct Ascorbic *asc, struct Particle *particle ) {
	int place;
	
	assert( asc );
	assert( asc->out );
	assert( asc->frameheight > -1 );
	
	/* empty integer */
	if( particle == NULL ) fprintf( asc->out, " PUSH 0;\t//integer\n" );
	
	/* integer particle */
	else if( particle->type == integer ) fprintf( asc->out, " PUSH %d;\t//integer\n", (int)particle->data );
	
	/* other particle */
	else return Ascorbic_ErrorParticle( asc, particle, "cannot convert to integer type" );

	
	/* frame element */
	place = asc->frameheight;
	asc->frameheight++;
	
	return place;
	}

int Assembly_VariablePlace( struct Ascorbic *asc, int type, struct Particle *particle ) {
	struct Symbol *symbol;
	
	assert( type > -1 );
	assert( particle );
	assert( particle->type == identifier );
	assert( particle->data );
	
	/* find symbol */
	symbol = Assembly_FindSymbol( asc, particle->data );
	
	/* symbol not found */
	if( symbol == NULL ) return Ascorbic_ErrorParticle( asc, particle, "unknown identifier" );
	
	/* non-matching types */
	if( symbol->type != type ) return Ascorbic_ErrorParticle( asc, particle, "variable has incorrect type" );
	
	assert( symbol->place > -1 );
	return symbol->place;
	}

int Assembly_Variable( struct Ascorbic *asc, int type, struct Particle *particle ) {
	int var, place;
	
	assert( asc );
	assert( asc->out );
	assert( particle );
	assert( particle->type == identifier );
	assert( particle->data );
	
	/* find variable */
	var = Assembly_VariablePlace( asc, type, particle );
	
	/* variable not found */
	if( var == -1 ) return -1;
	
	/* frame element */
	place = asc->frameheight;
	
	/* make new element */
	fprintf( asc->out, " PUSH 0;\t//copy %s\n", particle->data );
	asc->frameheight++;
	
	/* copy value from variable */
	fprintf( asc->out, " ASSIGN %d %d;\n", var, place );

	return place;
	}

int Assembly_Generic( struct Ascorbic *asc, int type, struct Particle *particle ) {
	int a, b, t, m, element;
	char *op = NULL, *opname = NULL;
	
	assert( asc );
	assert( asc->out );
	assert( type > -1 );
	assert( particle );
	assert( particle->childnum == 2 );
	assert( particle->child );
	
	element = asc->frameheight;
	
	/* operation type */
	switch( particle->type ) {
		case or: op = "OR"; opname = "bitwise or"; break;
		case xor: op = "XOR"; opname = "bitwise xor"; break;
		case gt: op = "GREATERTHAN"; opname = "greater than"; break;
		case lt: op = "GREATERTHAN"; opname = "less than"; break;
		case equal: op = "EQUAL"; opname = "equal (bits identical)"; break;
		case subtract: op = "SUBTRACT"; opname = "subtract"; break;
		case add: op = "ADD"; opname = "add"; break;
		case multiply: op = "MULTIPLY"; opname = "multiply"; break;
		case divide: op = "DIVIDE"; opname = "divide"; break;
		case lsh: op = "ROTATE"; opname = "left shift"; break;
		case rsh: op = "ROTATE"; opname = "right shift"; break;
		default: assert( 0 );
		}
	
	/* get first value */
	a = Assembly_Value( asc, type, particle->child[0] );
	assert( a < asc->frameheight );
	
	/* no value */
	if( a == -1 ) return -1;
	
	/* get second value */
	b = Assembly_Value( asc, type, particle->child[1] );
	assert( b < asc->frameheight );
	
	/* no value */
	if( b == -1 ) return -1;
	
	/* less than */
	if( particle->type == lt ) {
		/* exchange a and b */
		t = a;
		a = b;
		b = t;
		}
	
	/* integer operation */
	if( type == inttype ) {
		/* left shift */
		if( particle->type == lsh ) {
			/* b := 0 - b */
			t = asc->frameheight;
			fprintf( asc->out, " PUSH 0;\n" );
			asc->frameheight++;
			fprintf( asc->out, " SUBTRACT %d %d;\n", t, b );
			b = asc->frameheight;
			asc->frameheight++;
			}
		
		/* instruction */
		fprintf( asc->out, " %s %d %d;\t//%s\n", op, a, b, opname );
		a = asc->frameheight;
		asc->frameheight++;
		
		/* left or right shift */
		if( particle->type == lsh || particle->type == rsh ) {
			/* m := rotate( 1, b ) - 1 */
			t = asc->frameheight;
			fprintf( asc->out, " PUSH 1;\n" );
			asc->frameheight++;
			fprintf( asc->out, " ROTATE %d %d;", t, b );
			m = asc->frameheight;
			asc->frameheight++;
			fprintf( asc->out, " SUBTRACT %d %d;\n", m, t );
			m = asc->frameheight;
			asc->frameheight++;
			
			/* a := a*(b > -32) */
			t = asc->frameheight;
			fprintf( asc->out, " PUSH -32;\n" );
			asc->frameheight++;
			fprintf( asc->out, " GREATERTHAN %d %d;", b, t );
			t = asc->frameheight;
			asc->frameheight++;
			fprintf( asc->out, " MULTIPLY %d %d;\n", t, a );
			a = asc->frameheight;
			asc->frameheight++;
			
			/* a := a*(32 > b) */
			t = asc->frameheight;
			fprintf( asc->out, " PUSH 32;\n" );
			asc->frameheight++;
			fprintf( asc->out, " GREATERTHAN %d %d;", t, b );
			t = asc->frameheight;
			asc->frameheight++;
			fprintf( asc->out, " MULTIPLY %d %d;\n", t, a );
			a = asc->frameheight;
			asc->frameheight++;
			
			/* b := 1 > b */
			t = asc->frameheight;
			fprintf( asc->out, " PUSH 1;\n" );
			asc->frameheight++;
			fprintf( asc->out, " GREATERTHAN %d %d;", t, b );
			b = asc->frameheight;
			asc->frameheight++;
			
			/* b = (-1)*b */
			t = asc->frameheight;
			fprintf( asc->out, " PUSH -1;\n" );
			asc->frameheight++;
			fprintf( asc->out, " MULTIPLY %d %d;", t, b );
			b = asc->frameheight;
			asc->frameheight++;
			
			/* m = b ^ m */
			fprintf( asc->out, " XOR %d %d;", b, m );
			m = asc->frameheight;
			asc->frameheight++;
			
			/* a = a & m */
			fprintf( asc->out, " AND %d %d;", a, m );
			a = asc->frameheight;
			asc->frameheight++;
			}
		
		/* copy sum for returning */
		fprintf( asc->out, " ASSIGN %d %d;\n", a, element );
		
		/* discard non-returning elements */
		while( asc->frameheight > element + 1 ) {
			fprintf( asc->out, " POP;\n" );
			asc->frameheight--;
			}
		
		assert( element + 1 == asc->frameheight );
		return element;
		}
	}

int Assembly_Value( struct Ascorbic *asc, int type, struct Particle *particle ) {
	int ret;
	
	assert( asc );
	assert( asc->out );
	assert( type > -1 );
	
	/* no initial value */
	if( particle == NULL ) {
		switch( type ) {
			case inttype:
				return Assembly_Integer( asc, particle );
			}
		assert( 0 );
		}
	
	
	/* particle type */
	switch( particle->type ) {
		case integer:
			/* did not request integer */
			if( type != inttype ) return Ascorbic_ErrorParticle( asc, particle, "cannot convert from integer" );
		
			/* produce the integer */
			ret = Assembly_Integer( asc, particle );
			break;
		case identifier:
			ret = Assembly_Variable( asc, type, particle );
			break;
		case or:
		case xor:
		case gt:
		case lt:
		case equal:
		case subtract:
		case add:
		case multiply:
		case divide:
		case lsh:
		case rsh:
			ret = Assembly_Generic( asc, type, particle );
			break;
		default:
			assert( 0 );
		}
	
	assert( ret < asc->frameheight );
	return ret;
	}

int Assembly_Definition( struct Ascorbic *asc, struct Particle *keyword ) {
	struct Particle *label, *initial;
	int type, ret;
	
	/* keyword */
	assert( keyword );
	assert( keyword->childnum == 1 || keyword->childnum == 2 );
	assert( keyword->child );
	
	/* label */
	label = keyword->child[0];
	assert( label );
	assert( label->type == identifier );
	assert( label->data );
	
	/* initial value exists */
	if( keyword->childnum == 2 ) {
		initial = keyword->child[1];
		assert( initial );
		}
	/* initial value does not exist */
	else initial = NULL;
	
	/* variable type */
	switch( keyword->type ) {
		case intkeyword:
			type = inttype;
			break;
		case floatkeyword:
			type = floattype;
			break;
		case stringkeyword:
			type = stringtype;
			break;
		default: assert( 0 );
		}
	
	/* produce the initial value */
	ret = Assembly_Value( asc, type, initial );
	
	/* production failed */
	if( ret == -1 ) return -1;
	
	/* add symbol */
	ret = Assembly_AddSymbol( asc, type, label->data, ret, 0, 0 );
	
	/* symbol with same name already exists */
	if( ret == -1 ) return Ascorbic_ErrorParticle( asc, label, "symbol with this name already exists" );
	
	return 1;
	}

int Assembly_Assign( struct Ascorbic *asc, struct Particle *particle ) {
	struct Symbol *symbol;
	int val;
	
	assert( asc );
	assert( asc->out );
	assert( particle );
	assert( particle->type == assign );
	assert( particle->childnum == 2 );
	assert( particle->child );
	assert( particle->child[0] );
	assert( particle->child[0]->type == identifier );
	assert( particle->child[0]->data );
	assert( particle->child[1] );
	
	/* find destination symbol */
	symbol = Assembly_FindSymbol( asc, particle->child[0]->data );
	
	/* symbol not found */
	if( symbol == NULL ) return Ascorbic_ErrorParticle( asc, particle, "unknown identifier" );
	
	/* value to assign */
	val = Assembly_Value( asc, symbol->type, particle->child[1] );
	
	/* no value */
	if( val == -1 ) return -1;
	
	/* integer assignment */
	if( symbol->type == inttype ) {
		/* assign */
		fprintf( asc->out, " ASSIGN %d %d;\t//integer assignment\n", val, symbol->place );
		
		/* discard val */
		assert( val == asc->frameheight - 1 );
		fprintf( asc->out, " POP;\n" );
		asc->frameheight--;
		return 1;
		}
	
	return Ascorbic_ErrorParticle( asc, particle, "unknown variable type" );
	}

int Assembly_Statement( struct Ascorbic *asc, struct Particle *particle ) {
	assert( asc );
	assert( asc->out );
	assert( particle );
	
	switch( particle->type ) {
		case intkeyword:
		case floatkeyword:
		case stringkeyword:
			return Assembly_Definition( asc, particle );
		case assign:
			return Assembly_Assign( asc, particle );
			break;
		case identifier:
			/* TODO: call Assembly_Function */
			fprintf( asc->out, "//sub call\n" );
			break;
		case returnkeyword:
			/* TODO: call Assembly_Return */
			return 0;
		default: assert( 0 );
		}
	
	return 1;
	}

int Assembly_Sub( struct Ascorbic *asc, struct Particle *particle ) {
	struct Particle *label, *block;
	struct Symbol *symbol;
	int s, ret;
	
	assert( asc );
	assert( asc->out );
	assert( particle );
	assert( particle->type == subkeyword );
	assert( particle->childnum == 2 );
	
	/* label */
	label = particle->child[0];
	assert( label );
	assert( label->type == identifier );
	assert( label->data );
	
	/* statement block */
	block = particle->child[1];
	assert( block );
	assert( block->type == statementblock );
	assert( block->place == asc->defncount );
	asc->defncount++;
	
	/* defn directive */
	fprintf( asc->out, "DEFN 2 1;\t//(%d) sub %s\n", block->place, label->data );
	
	/* pop our defn-id */
	fprintf( asc->out, " POP;\t\t//pop this defn-id\n" );
	
	/* new frame */
	asc->frameheight = 1;
	
	/* TODO: register parameters in symbol list */
	
	/* statements */
	for( s = 0; s < block->childnum; s++ ) {
		/* produce statement */
		ret = Assembly_Statement( asc, block->child[s] );
		
		/* production failed */
		if( ret == -1 ) return -1;
		
		/* produced return */
		if( ret == 0 ) break;
		}
	
	/* purge local scope */
	Assembly_PurgeLocalScope( asc );
	
	/* destination defn-id is at top of frame */
	fprintf( asc->out, " JUMP 0;\t//return\n\n" );
	return 1;
	}

void Assembly_WalkForBlocks( struct Ascorbic *asc, struct Particle *particle ) {
	struct Particle *child;
	int c;
	
	assert( asc );
	assert( asc->defncount > 0 );
	assert( particle );
	
	/* particle is block */
	if( particle->type == statementblock ) {
		/* defn id of block */
		particle->place = asc->defncount;
		asc->defncount++;
		}
	
	/* descend child nodes */
	for( c = 0; c < particle->childnum; c++ ) Assembly_WalkForBlocks( asc, particle->child[c] );
	}

int Assembly_SetupSymbolTable( struct Ascorbic *asc ) {
	struct Particle *particle, *child;
	int c, ret;
	
	assert( asc );
	assert( asc->tree );
	assert( asc->tree->type == vitaminc );
	assert( asc->tree->childnum > 0 );
	assert( asc->symbols == NULL );
	assert( asc->defncount == -1 );
	assert( asc->frameheight == -1 ); 

	/* make symbol list */
	asc->symbols = (struct SymbolList *)malloc( sizeof( struct SymbolList ) );
	assert( asc->symbols );
	asc->symbols->list = NULL;
	asc->symbols->num = 0;
	asc->symbols->space = 0;
	asc->defncount = 3;
	asc->frameheight = 0;
	
	/* each sub */
	for( c = 0; c < asc->tree->childnum; c++ ) {
		/* sub */
		particle = asc->tree->child[c];
		assert( particle );
		assert( particle->type == subkeyword );
		assert( particle->childnum > 1 );
		assert( particle->child );
		
		/* identifier */
		child = particle->child[0];
		assert( child );
		assert( child->type == identifier );
		assert( child->data );
		
		/* add sub to symbol list */
		ret = Assembly_AddSymbol( asc, subtype, child->data, asc->defncount, 0, 0 );
	
		/* symbol with same name already exists */
		if( ret == -1 ) return Ascorbic_ErrorParticle( asc, child, "symbol with this name already exists" );
		
		/* count code blocks */
		Assembly_WalkForBlocks( asc, particle );
		}

	/* all code blocks are counted */
	asc->numdefns = asc->defncount;
	asc->defncount = 3;
	
	return 1;
	}

int Assembly_EntryExit( struct Ascorbic *asc ) {
	struct Symbol *entrysub;

	assert( asc );
	assert( asc->out );

	/* find symbol for main sub */
	entrysub = Assembly_FindSymbol( asc, "main" );
	
	/* main sub not found */
	if( entrysub == NULL || entrysub->type != subtype ) return Ascorbic_Error( asc, "program has no `main' sub" );
	
	/* produce entry point defn */
	assert( entrysub->place > 1 );
	fprintf( asc->out, "DEFN 0 2;\t//(1) program entry point\n" );
	fprintf( asc->out, " PUSH 2;\t//defn-id of program exit point\n" );
	fprintf( asc->out, " PUSH %d;\t//defn-id of main sub\n", entrysub->place );
	fprintf( asc->out, " JUMP 1;\t//call main sub\n" );

	/* produce exit point defn */
	fprintf( asc->out, "\nDEFN 1 0;\t//(2) program exit point\n" );
	fprintf( asc->out, " POP;\t\t//pop our defn-id\n" );
	fprintf( asc->out, " HALT;\t\t//end of program\n\n" );

	return 1;
	}

int Assembly_Write( struct Ascorbic *asc ) {
	int c, ret;
	
	assert( asc );
	assert( asc->tree );
	assert( asc->tree->type == vitaminc );
	assert( asc->tree->childnum > 0 );
	
	/* set up the symbol table, scan subs, and count blocks */
	ret = Assembly_SetupSymbolTable( asc );
	
	/* setup failed */
	if( ret == -1 ) return -1;
	
	/* produce the entry and exit defns */
	ret = Assembly_EntryExit( asc );
	
	/* produce failed */
	if( ret == -1 ) return -1;
	
	/* each sub */
	for( c = 0; c < asc->tree->childnum; c++ ) {
		assert( asc->tree->child[c]->type == subkeyword );
		
		/* produce the sub */
		ret = Assembly_Sub( asc, asc->tree->child[c] );
		
		/* produce failed */
		if( ret == -1 ) return -1;
		}
	
	printf( "Assembly_Write() successful\n" );
	return 1;
	}
