/* Copyright (C) 2001 Michael Leonhard
 * Mike Leonhard
 * mike at tamale dot net
 * http://tamale.net/
 */

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include "sebae.h"

int Sebae_Assembly_GetFloat( struct SebaeVM *vm, struct Directive *directive, int t ) {
	double val;
	float *store;
	char *endptr;
	
	assert( directive );
	assert( t > 0 );
	assert( t < directive->num );
	
	/* initialize errno */
	errno = 0;
	
	/* convert token to double */
	val = strtod( directive->token[t], &endptr );
	
	/* not entirely a number */
	if( *endptr != 0 ) return -1;
	
	/* check for underflow */
	if( errno == ERANGE && val == 0 ) return Sebae_Error( vm, "invalid assembly.  floating point number underflow" );
	
	/* check overflow */
	if( errno == ERANGE && val > 0 ) return Sebae_Error( vm, "invalid assembly.  floating point number overflow" );
	
	/* store number */
	store = (float *)&directive->i[t];
	*store = (float)val;
	return 1;
	}

int Sebae_Assembly_GetInteger( struct SebaeVM *vm, struct Directive *directive, int t ) {
	long int val;
	char *endptr;
	
	assert( directive );
	assert( t > 0 );
	
	/* check that token is present */
	if( t >= directive->num ) return Sebae_Error( vm, "invalid assembly.  expected integer" );
	
	/* initialize errno */
	errno = 0;
	
	/* convert token to integer */
	val = strtol( directive->token[t], &endptr, 0 );
	
	/* not entirely a number */
	if( *endptr != 0 ) return Sebae_Error( vm, "invalid assembly.  expected integer" );
	
	/* check for underflow */
	if( errno == ERANGE && val < 0 ) return Sebae_Error( vm, "invalid assembly.  integer underflow" );
	if( val < -2147483647 ) return Sebae_Error( vm, "invalid assembly.  integer underflow" );
	
	/* check overflow */
	if( errno == ERANGE && val > 0 ) return Sebae_Error( vm, "invalid assembly.  integer overflow" );
	if( val > 2147483647 ) return Sebae_Error( vm, "invalid assembly.  integer overflow" );
	
	/* store integer */
	directive->i[t] = (int)val;
	return 1;
	}

int Sebae_Assembly_IsInteger( struct SebaeVM *vm, struct Directive *directive, int t ) {
	long int val;
	char *endptr;
	
	assert( directive );
	assert( t > 0 );
	
	/* check that token is present */
	if( t >= directive->num ) return -1;
	
	/* initialize errno */
	errno = 0;
	
	/* convert token to integer */
	val = strtol( directive->token[t], &endptr, 0 );
	
	/* not entirely a number */
	if( *endptr != 0 ) return -1;
	
	/* store integer */
	directive->i[t] = (int)val;
	return 1;
	}

int Sebae_Assembly_LoadFile( struct SebaeVM *vm, char *fname ) {
	int ret, p;
	
	/* create pipe from file */
	p = Sebae_Pipe_FromFile( vm, fname );
	
	/* pipe creation failed */
	if( p == -1 ) return -1;

	/* read the assembly from pipe 0 */
	ret = Sebae_Assembly_Read( vm, p );
	
	/* assembly read failed */
	if( ret == -1 ) return -1;
	
	return 1;
	}

char *Sebae_Assembly_Process( struct SebaeVM *vm, char *text ) {
	char *here;
	int ret, l;
	
	assert( text );
	
	/* skip leading whitespace */
	while( *text == ' ' ) text++;

	/* loop for each statement */
	here = text;
	while( *here != 0 ) {
		/* end of statement */
		if( *here == ';' ) {
			/* terminate the statment */
			*here = 0;
			
			/* process the statement */
			ret = Sebae_Assembly_ProcessStatement( vm, text );
			
			/* process failed */
			if( ret == -1 ) return NULL;
			
			/* next statement will begin here */
			here++;
			
			/* do next statement */
			text = here;
			}
		/* quote */
		else if( *here == '"' ) {
			/* skip over opening quote */
			here++;
			
			/* lines in quote */
			l = 0;
			
			/* find closing quote */
			while( (*here != 0) && (*here != '"') ) {
				/* keep track of lines */
				if( *here == '\n' ) l++;
				
				here++;
				}

			/* quoted string is incomplete */
			/* go back and read more from the file */
			if( *here == 0 ) return text;
			
			/* skip past closing quote */
			here++;
			
			/* keep line counter correct */
			vm->linenumber += l;
			}
		/* comment */
		else if( here[0] == '/' && here[1] == '/' ) {
			/* iterate over comment */
			while( *here != 0 && *here != '\r' && *here != '\n' ) {
				/* blank out the comment with spaces */
				*here = ' ';
				here++;
				}
			
			/* save incomplete comments */
			if( *here == 0 ) {
				/* put the comment symbols back in */
				here[-1] = '/';
				here[-2] = '/';
				/* go back to read more data */
				return text;
				}
			}
		else if( *here == '\r' || *here == '\n' || *here == '\t' || *here == ',' ) {
			/* keep line counter correct */
			if( *here == '\n' ) vm->linenumber++;

			/* convert all whitespace to ' ' */
			*here = ' ';
			here++;
			}
		/* some other character */
		else here++;
		}
	
	/* skip trailing whitespace */
	while( *text == ' ' ) text++;

	return text;
	}
	
int Sebae_Assembly_ProcessDirective( struct SebaeVM *vm, struct Directive *directive ) {
	int d, b;
	
	assert( directive );
	
	/* find directive */
	for( d = 0; d < dnum; d++ ) {
		if( strcmp( directive->token[0], iset[d].name ) == 0 ) break;
		}
	
	/* directive not found */
	if( d == dnum ) return Sebae_Error( vm, "invalid assembly.  unrecognized directive" );
	
	/* call ProduceFunc */
	return (*iset[d].produce)( vm, &iset[d], directive );
	}

int Sebae_Assembly_ProcessStatement( struct SebaeVM *vm, char *statement ) {
	char *start;
	int len;
	struct Directive directive;
	
	assert( statement );
	
	directive.num = 0;
	
	/* skip leading whitespace */
	while( *statement == ' ' ) statement++;

	/* split string up into tokens */
	while( *statement != 0 && directive.num < 4 ) {
		/* token begins here */
		start = statement;
		
		/* token is string */
		if( *statement == '"' ) {
			/* skip opening quote */
			statement++;
			
			/* find end of string */
			while( *statement != '"' ) {
				assert( *statement != 0 );
				statement++;
				}
			
			/* skip closing quote */
			statement++;
			}
		
		/* normal token */
		else {
			/* find end of token */
			while( *statement != 0 && *statement != ' ' && *statement != '"' ) statement++;
			}
		
		/* token length */
		len = statement - start;
		
		/* get memory for token */
		directive.token[directive.num] = (char *)malloc( len + 1 );
		assert( directive.token[directive.num] );
		
		/* copy token */
		memcpy( directive.token[directive.num], start, len );
		
		/* null terminate */
		directive.token[directive.num][len] = 0;
		
		/* done with this token */
		directive.num++;

		/* skip trailing whitespace */
		while( *statement == ' ' ) statement++;
		}
	
	/* too many tokens */
	if( *statement != 0 ) return Sebae_Error( vm, "invalid assembly.  too many tokens in statement" );
	
	/* process tokens */
	return Sebae_Assembly_ProcessDirective( vm, &directive );
	}

int Sebae_Assembly_ProduceDATA( struct SebaeVM *vm, struct InstructionSet *ientry, struct Directive *directive ) {
	int len, p;
	char *here;

	assert( vm );
	assert( ientry );
	assert( directive );
	
	/* check that we're not in a defn */
	if( vm->newdefn != NULL ) return Sebae_Error( vm, "invalid assembly.  previous DEFN not terminated" );
	
	/* check token existence */
	if( directive->num != 2 ) return Sebae_Error( vm, "invalid assembly.  DEFN requires consume and produce" );
	
	/* check specified filename */
	if( directive->token[1][0] != '"' ) return Sebae_Error( vm, "invalid assembly.  DATA filename must be quoted" );
	
	/* filename */
	here = directive->token[1];
	
	/* skip over opening quote */
	here++;
	
	/* length of filename */
	len = strlen( here );
	
	/* leave out trailing quote */
	len--;
	
	/* check filename length */
	if( len < 1 ) return Sebae_Error( vm, "invalid assembly.  invalid filename specified" );
	
	/* null out the trailing quote */
	here[len] = 0;
	
	/* printf( "  DATA \"%s\";\n", here );/**/
	
	/* create pipe from file */
	p = Sebae_Pipe_FromFile( vm, here );
	
	/* pipe creation failed */
	if( p == -1 ) return -1;
	
	/* data block is on this pipe */
	vm->datapipe = p;

	return 1;
	}

int Sebae_Assembly_ProduceDEFN( struct SebaeVM *vm, struct InstructionSet *ientry, struct Directive *directive ) {
	assert( vm );
	assert( ientry );
	assert( directive );
	
	/* check that we're not in a defn */
	if( vm->newdefn != NULL ) return Sebae_Error( vm, "invalid assembly.  previous DEFN not terminated" );
	
	/* check token existence */
	if( directive->num != 3 ) return Sebae_Error( vm, "invalid assembly.  DEFN requires consume and produce" );
	
	/* check consume */
	if( Sebae_Assembly_GetInteger( vm, directive, 1 ) == -1 ) return -1;
	if( directive->i[1] < 0 || directive->i[1] > 255 ) return Sebae_Error( vm, "invalid assembly.  invalid consume for DEFN directive" );
	
	/* check produce */
	if( Sebae_Assembly_GetInteger( vm, directive, 2 ) == -1 ) return -1;
	if( directive->i[2] < 0 || directive->i[2] > 255 ) return Sebae_Error( vm, "invalid assembly.  invalid produce for DEFN directive" );
	
	/* new defn */
	vm->newdefn = Sebae_VM_NewDefn( vm );
	assert( vm->newdefn );
	vm->newdefn->vector = vm->vector;
	vm->newdefn->consume = directive->i[1];
	vm->newdefn->produce = directive->i[2];
	vm->newdefn->maxheight = vm->newdefn->consume;
	vm->newdefn->bytes = 2;
	
	/* printf( "DEFN %d %d;\n", vm->newdefn->consume, vm->newdefn->produce );/**/
	
	/* reset frame height */
	vm->height = vm->newdefn->consume;
	
	return 1;
	}

int Sebae_Assembly_ProduceDEFNTERMINAL( struct SebaeVM *vm, struct InstructionSet *ientry, struct Directive *directive ) {
	int ret;
	
	assert( vm );
	assert( ientry );
	assert( directive );
	
	/* check that we're in a defn */
	if( vm->newdefn == NULL ) return Sebae_Error( vm, "invalid assembly.  instruction found outside of DEFN" );
	
	/* check frame */
	if( vm->height != vm->newdefn->produce ) return Sebae_Error( vm, "invalid assembly.  stack height does not match defn's produce value" );

	/* add the instruction */
	Sebae_VM_AddInstruction( vm->newdefn, ientry->num, 1 );
	/* printf( "  %s", ientry->name );/**/
	
	/* read the parameters */
	ret = Sebae_Assembly_ReadParameters( vm, ientry, directive );
	
	/* read failed */
	if( ret == -1 ) return -1;
	
	/* printf( ";\n\n" );/**/

	/* done with this defn */
	vm->newdefn = NULL;
	
	return 1;
	}

int Sebae_Assembly_ProduceGENERIC( struct SebaeVM *vm, struct InstructionSet *ientry, struct Directive *directive ) {
	assert( vm );
	assert( ientry );
	assert( directive );
	
	/* check that we're in a defn */
	if( vm->newdefn == NULL ) return Sebae_Error( vm, "invalid assembly.  instruction found outside of DEFN" );
	
	/* add the instruction */
	Sebae_VM_AddInstruction( vm->newdefn, ientry->num, 1 );
	/* printf( "  %s", ientry->name );/**/
	
	/* read the parameters */
	if( Sebae_Assembly_ReadParameters( vm, ientry, directive ) == -1 ) return -1;
	
	/* printf( ";\n" );/**/

	/* adjust the stack frame height */
	vm->height += ientry->stackeffect;
	
	/* check for stack frame overflow */
	if( vm->height > 255 ) return Sebae_Error( vm, "invalid assembly.  stack frame reaches a height of >255" );

	/* stack frame is higher than any previous time */
	if( vm->height > vm->newdefn->maxheight ) vm->newdefn->maxheight = vm->height;
	
	return 1;
	}

int Sebae_Assembly_Read( struct SebaeVM *vm, int p ) {
	int size = 0, space = BUFFERSIZE, ret;
	char *text = NULL, *here, *newbuffer;

	assert( vm );
	assert( vm->newdefn == NULL );

	/* vector for new defns */
	vm->vector = vm->numdefns;
	
	/* reset line number */
	vm->linenumber = 1;
	
	/* malloc buffer */
	text = (char *)malloc( space + 2 );/* extra \n and terminator */
	assert( text );

	/* loop and read file */
	while( 1 ) {
		/* read more of the file */
		ret = Sebae_Pipe_Read( vm, p, (unsigned char *)text + size, BUFFERSIZE - size );

		/* read failed */
		if( ret == -1 ) return -1;

		/* buffer now holds more text */
		size += ret;
		
		/* null terminate */
		text[size] = 0;
		
		/* end of file */
		if( ret == 0 ) break;
		
		/* check text for illegal null byte */
		for( ret = 0; ret < size; ret++ ) {
			if( text[ret] == '\0' ) return Sebae_Error( vm, "invalid assembly.  null byte found" );
			}
		
		/* process the text */
		here = Sebae_Assembly_Process( vm, text );
		if( !here ) return -1;
		
		size = strlen( here );

		/* new buffer will be BUFFERSIZE bytes bigger than is needed */
		space = size + BUFFERSIZE;
		newbuffer = (char *)malloc( space + 2 );/* extra \n and terminator */
		assert( newbuffer );
		
		/* copy the unprocessed text to new buffer */
		memcpy( newbuffer, here, size );
		newbuffer[size] = 0;
		
		/* swap buffers */
		free( text );
		text = newbuffer;
		}

	/* add extra \n at end of file                    */
	/* this ensures that last token will be processed */
	text[size] = '\n';
	size++;
	text[size] = 0;

	/* do last chunk */
	here = Sebae_Assembly_Process( vm, text );
	if( *here != 0 ) return Sebae_Error( vm, "invalid assembly.  assembly code ends abruptly" );
	
	/* cleanup */
	free( text );
	
	/* reset line number */
	vm->linenumber = -1;
	
	return 1;
	}

int Sebae_Assembly_ReadParameters( struct SebaeVM *vm, struct InstructionSet *ientry, struct Directive *directive ) {
	int p, parm;
	float f;
	
	assert( vm );
	assert( vm->newdefn );
	
	/* no parameters */
	if( ientry->parameters == 0 && directive->num != 1 ) return Sebae_Error( vm, "invalid assembly.  too many parameters" );

	/* integer parameters */
	else if( ientry->parameters > 0 && ientry->parameters < 4 ) {
		/* check parameters */
		if( ientry->parameters != directive->num - 1 ) return Sebae_Error( vm, "invalid assembly.  incorrect number of parameters" );
		
		/* output each parameter */
		for( p = 1; p < ientry->parameters + 1; p++ ) {
			/* convert integer */
			if( Sebae_Assembly_GetInteger( vm, directive, p ) == -1 ) return Sebae_Error( vm, "invalid assembly.  expected integer" );

			/* add the parameter */
			Sebae_VM_AddInstruction( vm->newdefn, directive->i[p], 1 );
			/* printf( " %d", directive->i[p] );/**/
			}
		}
	
	/* one parameter, integer or floating point */
	else if( ientry->parameters == -1 ) {
		/* check for floating point */
		if( Sebae_Assembly_IsInteger( vm, directive, 1 ) == -1 && Sebae_Assembly_GetFloat( vm, directive, 1 ) == 1 ) {
			f = *(float *)&directive->i[1];
			/* printf( " %f", f );/**/
			}
		
		/* check for integer */
		else if( Sebae_Assembly_GetInteger( vm, directive, 1 ) == 1 ) {
			/* printf( " %d", directive->i[1] );/**/
			}
		
		/* add the parameter */
		Sebae_VM_AddInstruction( vm->newdefn, directive->i[1], 4 );
		}
		
	return 1;
	}

int Sebae_Assembly_Write( struct SebaeVM *vm, int p ) {
	int d, ret;
	
	assert( vm );
	assert( vm->numdefns > 0 );
	assert( vm->defnlist );

	/* write defns */
	for( d = 0; d < vm->numdefns; d++ ) {
		
		/* defn is valid */
		if( vm->defnlist[d].vector != -1 ) {
			/* write the defn */
			ret = Sebae_Assembly_WriteDefn( vm, p, &vm->defnlist[d] );
			
			/* defn write failed */
			if( ret == -1 ) return -1;
			}
		}
	
	/* all defns written */
	return 1;
	}

int Sebae_Assembly_WriteDefn( struct SebaeVM *vm, int p, struct SebaeDefn *defn ) {
	struct InstructionSet *icode;
	char text[256], comment[128], render[32];
	int i, opcode, parm, ret, height;
	
	assert( defn );
	assert( defn->vector > -1 );
	
	/* defn directive */
	snprintf( text, 255, "\nDEFN %d %d;\n", defn->consume, defn->produce );
	text[255] = 0;
	
	/* write defn directive */
	ret = Sebae_Pipe_Write( vm, p, (unsigned char *)text, strlen( text ) );
	
	/* write failed */
	if( ret == -1 ) return -1;
	
	/* printf( "%s", text );/**/
	
	/* stack frame */
	height = defn->consume;

	/* write instructions */
	i = 0;
	while( (defn->numinsns - i) > 0 ) {
		/* initialize */
		comment[0] = 0;
	
		/* opcode */
		opcode = defn->insnlist[i++];
		
		/* sanity */
		assert( opcode > -1 );
		assert( opcode < inum );
	
		/* find the instruction */
		icode = &iset[opcode];

		/* directive */
		snprintf( text, 255, "  %s", icode->name );
		text[255] = 0;
	
		/* write the parameters */
		for( parm = 0; parm < icode->parameters; parm++ ) {
			/* sanity */
			assert( defn->numinsns - i > 0 );
			
			/* parameter */
			snprintf( render, 31, " %d", defn->insnlist[i++] );
			render[31] = 0;
			
			/* add parameter to directive */
			strncat( text, render, 255 );
			text[255] = 0;
			}
		
		/* 32bit parm */
		if( icode->parameters == -1 ) {
			/* sanity */
			assert( defn->numinsns - i > 0 );
			
			/* parameter */
			parm = defn->insnlist[i++];
			snprintf( render, 31, " 0x%08X", parm );
			render[31] = 0;
			
			/* comment */
			snprintf( comment, 127, "// %d or %f", parm, *(float *)&parm );
			comment[127] = 0;
			
			/* add parameter to directive */
			strncat( text, render, 255 );
			text[255] = 0;
			}
		
		/* terminate directive */
		strncat( text, ";", 255 );
		text[255] = 0;
		
		/* append comment */
		strncat( text, comment, 255 );
		text[255] = 0;

		/* end of line */
		strncat( text, "\n", 255 );
		text[255] = 0;
		
		/* write directive */
		ret = Sebae_Pipe_Write( vm, p, (unsigned char *)text, strlen( text ) );
	
		/* write failed */
		if( ret == -1 ) return -1;
	
		/* adjust stack frame */
		height += icode->stackeffect;
		
		/* sanity */
		assert( height < 256 );
		
		/* printf( "%s", text );/**/
		
		/* defn terminal instruction */
		if( opcode < 2 ) break;
		}

	/* sanity */
	assert( i == defn->numinsns );
	assert( height == defn->produce );

	return 1;
	}

int Sebae_Assembly_WriteFile( struct SebaeVM *vm, char *fname ) {
	int ret, p, d;
	char *binname, *buffer, *dir;

	assert( vm );
	
	/* create pipe to file */
	p = Sebae_Pipe_ToFile( vm, fname );
	
	/* pipe creation failed */
	if( p == -1 ) return -1;

	/* write the assembly code to the pipe */
	ret = Sebae_Assembly_Write( vm, p );
	
	/* write failed */
	if( ret == -1 ) return -1;
	
	/* data pipe is present */
	if( vm->datapipe > -1 ) {

		/* allocate for bin file name */
		binname = (char *)malloc( strlen( fname ) + 5 );/* fname.bin\0 */
		assert( binname );
		
		/* create bin file name */
		strcpy( binname, fname );
		strcat( binname, ".bin" );
		
		/* create pipe to file */
		d = Sebae_Pipe_ToFile( vm, binname );
		
		/* pipe creation failed */
		if( d == -1 ) return -1;
		
		/* allocate buffer */
		buffer = (char *)malloc( BUFFERSIZE );
		assert( buffer );
			
		/* copy data block to data file */
		while( 1 ) {
			/* read chunk */
			ret = Sebae_Pipe_Read( vm, vm->datapipe, (unsigned char *)buffer, BUFFERSIZE );
			if( ret == 0 ) break;
			if( ret == -1 ) return -1;
	
			ret = Sebae_Pipe_Write( vm, d, (unsigned char *)buffer, ret );
			if( ret == -1 ) return -1;
			}
		
		/* allocate for directive */
		ret = strlen( binname ) + 12;
		dir = (char *)malloc( ret + 1 );
		assert( dir );
		
		/* make directive */
		snprintf( dir, ret, "\nDATA \"%s\";\n\n", binname );
		dir[ret] = 0;
		
		/* write directive */
		ret = Sebae_Pipe_Write( vm, p, (unsigned char *)dir, strlen( dir ) );
		if( ret == -1 ) return -1;
		
		/* printf( "%s", dir );/**/
		
		/* clean up */
		free( binname );
		free( buffer );
		free( dir );
		
		/* close data file and free the pipe */
		ret = Sebae_Pipe_Free( vm, d );
		
		/* data commit failed */
		if( ret == -1 ) return -1;
		}
	
	/* close the assembly file and free the pipe */
	ret = Sebae_Pipe_Free( vm, p );
	
	/* data commit failed */
	if( ret == -1 ) return -1;

	return 1;
	}
