/*
 * opt - three pass optimiser for small-C compiler
 *         (Small C compiler is Z80 version on CP/M UG UK Vol 15)
 *
 * usage: optimise source-file
 *        sources-file is assumed to have extension .zsm
 *        output file has extension .opt
 *
 * Program written by:   R M Yorston
 *                       147a Evesham Road
 *                       Headless Cross
 *                       Redditch
 *                       Worcs  B97 4LJ
 *
 * This program is completely and utterly in the public domain:
 * do whatever you like with it except sell it.
 *
 */

#include crun.lib
#include conio.lib
#include file.lib
#include string.lib
#include numio.lib

#define TRUE	1
#define FALSE	0

#define NULL	0
#define TAB	9
#define LF	10
#define CR	13
#define EOF	26
#define SPACE	32

#define CONBUF	130

int Ichan, Ochan ;
char Line1[256], Line2[256], Line3[256], Line[256] ;
char *Lastline, *Thisline, *Nextline, *Temp, *Tail ;
int Saved[9] ;


#asm
;/*
; * match - match token against string
; *         returns NULL if no match
; *         else returns pointer to Tail of string
; */
;match( token, input )
;char *token, *input ;
;{
;	while ( *input++ == *token++ ) ;
;	--token ;
;	if ( *token == NULL )
;		return --input ;
;	else
;		return NULL ;
;}
;
match:
	POP BC
	POP HL		;HL is input
	POP DE		;DE is token
	PUSH DE
	PUSH HL
	PUSH BC
match1:
	LD A,(DE)	;fetch *token
	CP (HL)
	JP NZ,match2	;return if *token != *input
	INC HL
	INC DE
	JP match1
match2:
	OR A
	RET Z		;return input if *token == NULL
	LD HL,0		;else return 0
	RET
#endasm


main()
{
	char file1[20], file2[20] ;
	char *pin ;

	pin = CONBUF - 2 ;
	if( *pin <= -1 ) {
		puts( "No files given" ) ;
		exit() ;
	}
	pin = CONBUF ;
	strcpy( file1, pin ) ;
	strcat( file1, ".zsm" ) ;
	strcpy( file2, pin ) ;
	strcat( file2, ".opt") ;

	open( file1, file2 ) ;
	pass1() ;

	open( file2, file1 ) ;
	pass2() ;

	open( file1, file2 ) ;
	pass3() ;

}


/*
 * open - open f1 for reading, f2 for writing
 *        Ichan is input channel number
 *        Ochan is output channel number
 */
open( f1, f2 )
char *f1, *f2 ;
{
	Ichan = fopen( f1, "r" ) ;
	if ( Ichan == 0 ) {
		puts( "Cannot open file" ) ;
		exit() ;
	}

	Ochan = fopen( f2, "w" ) ;
	if ( Ochan == 0 ) {
		puts ( "Cannot open file" ) ;
		exit() ;
	}
}


/*
 * getline - fetch line from input channel
 *           puts line at pointer, returns FALSE on end of file
 *           returns TRUE for successful get
 */
getline( pointer )
char *pointer ;
{
	char ch ;

	while ( ((ch=getc(Ichan)) != EOF) & (eof()==0) ) {
		if( ch == CR ) {
			*pointer = NULL ;
			getc(Ichan) ;
			return TRUE ;
		}
		else
			*pointer++ = ch ;
	}
	return FALSE ;
}

/*
 * putline - send line to output file
 */
putline( pointer )
char *pointer ;
{
	while ( *pointer ) {
		putc( *pointer++, Ochan ) ;
	}
	putc( CR, Ochan ) ;
	putc( LF, Ochan ) ;
}


/*
 * allnum - checks if string consists entirely of decimal numbers
 *          returns TRUE if it does
 */
allnum( string )
char *string ;
{
	while ( *string ) {
		if ( (*string<'0') | (*string>'9') ) {
			return FALSE ;
		}
		++string ;
	}
	return TRUE ;
}


pass1()
{
	int i ;

	Saved[0] = Saved[1] = Saved[2] = Saved[3] = Saved[4] = 0 ;
	Saved[5] = Saved[6] = Saved[7] = 0 ;

	getline( Line1 ) ;
	getline( Line2 ) ;

	Lastline = Line1 ;
	Thisline = Line2 ;
	Nextline = Line3 ;

	while ( getline( Nextline ) ) {

		/* negative constant */
		if (strcmp( "	CALL ccneg", Nextline) == 0) {
			if ( (Tail=match("	LD HL,",Thisline)) != NULL ) {
				if ( allnum( Tail ) ) {
					strcpy( Line, "	LD HL,0-" ) ;
					strcat( Line, Tail ) ;
					strcpy( Thisline, Line ) ;
					getline( Nextline ) ;
					++Saved[0] ;
				}
			}
		}

		/* double constant */
		if ( strcmp("	ADD HL,HL",Nextline) == 0 ) {
		  if( (Tail=match("	LD HL,",Thisline)) != NULL ) {
		    if( allnum( Tail ) ) {
			strcpy( Line, "	LD HL,2*" ) ;
			strcat( Line, Tail ) ;
			strcpy( Thisline, Line ) ;
			getline( Nextline ) ;
			++Saved[4] ;
		    }
		  }
		}

		/* push and pop round ld hl */
		if ( (strcmp("	PUSH HL", Lastline) == 0) ) {
		  if ( (strcmp("	POP DE", Nextline) == 0) ) {
		    if ( match("	LD HL,",Thisline) != NULL ) {
				strcpy( Lastline, "	EX DE,HL" ) ;
				getline(Nextline) ;
				++Saved[1] ;
		    }
		  }
		}

		/* zero subscript */
		if ( strcmp("	EX DE,HL",Lastline) == 0 ) {
		  if ( (strcmp("	LD HL,0",Thisline) == 0) |
		       (strcmp("	LD HL,2*0",Thisline) == 0) )  {
		    if ( strcmp("	ADD HL,DE",Nextline) == 0 ) {
			strcpy(Lastline,"	LD D,H") ;
			strcpy(Thisline,"	LD E,L") ;
			getline(Nextline) ;
			++Saved[2] ;
		    }
		  }
		}

		/* one subscript */
		if ( strcmp("	EX DE,HL",Lastline) == 0 ) {
		  if ( strcmp("	LD HL,1",Thisline) == 0 ) {
		    if ( strcmp("	ADD HL,DE",Nextline) == 0 ) {
			strcpy(Lastline,"	LD D,H") ;
			strcpy(Thisline,"	LD E,L") ;
			strcpy(Nextline,"	INC HL") ;
			++Saved[3] ;
		    }
		  }
		}

		/* two subscript */
		if ( strcmp("	EX DE,HL",Lastline) == 0 ) {
		  if ( (strcmp("	LD HL,2",Thisline) == 0) |
		       (strcmp("	LD HL,2*1",Thisline) == 0) )  {
		    if ( strcmp("	ADD HL,DE",Nextline) == 0 ) {
			putline( "	LD D,H" ) ;
			strcpy(Lastline,"	LD E,L") ;
			strcpy( Thisline, "	INC HL" ) ;
			strcpy( Nextline, "	INC HL" ) ;
			++Saved[6] ;
		    }
		  }
		}

		/* fetch int from top of stack */
		if ( strcmp("	LD HL,0", Lastline) == 0 ) {
		  if ( strcmp("	ADD HL,SP", Thisline) == 0 ) {
		    if ( strcmp("	CALL ccgint", Nextline ) == 0 ) {
			strcpy( Lastline, "	POP HL" ) ;
			strcpy( Thisline, "	PUSH HL" ) ;
			getline( Nextline ) ;
			++Saved[7] ;
		    }
		  }
		}

		/* fetch second int from top of stack */
		if ( strcmp("	LD HL,2", Lastline) == 0 ) {
		  if ( strcmp("	ADD HL,SP", Thisline) == 0 ) {
		    if ( strcmp("	CALL ccgint", Nextline ) == 0 ) {
			putline( "	POP BC" ) ;
			strcpy( Lastline, "	POP HL" ) ;
			strcpy( Thisline, "	PUSH HL" ) ;
			strcpy( Nextline, "	PUSH BC " ) ;
			++Saved[5] ;
		    }
		  }
		}


		putline( Lastline ) ;
		Temp = Lastline ;
		Lastline = Thisline ;
		Thisline = Nextline ;
		Nextline = Temp ;
	}
	putline( Lastline ) ;
	putline( Thisline ) ;

	puts("Negative constants:           "); putdec(Saved[0]) ; crlf() ;
	puts("Double constants:             "); putdec(Saved[4]) ; crlf() ;
	puts("PUSH HL, POP DE round LD HL:  "); putdec(Saved[1]) ; crlf() ;
	puts("Zero subscript:               "); putdec(Saved[2]) ; crlf() ;
	puts("One subscript:                "); putdec(Saved[3]) ; crlf() ;
	puts("Two subscript:                "); putdec(Saved[6]) ; crlf() ;
	puts("Fetch top of stack:           "); putdec(Saved[7]) ; crlf() ;
	puts("Fetch 2nd top of stack:       "); putdec(Saved[5]) ; crlf() ;
	crlf() ;
	i = Saved[0]*3 + Saved[1] + Saved[2]*3 + Saved[3]*2 + Saved[4] ;
	i = i + Saved[5]*3 + Saved[6] + Saved[7]*5 ;
	puts("Total bytes saved: ") ; putdec( i ) ; crlf() ;

	fclose( Ichan ) ;
	fclose( Ochan ) ;
}

pass2()
{
	int i ;

	Saved[0] = Saved[1] = Saved[2] = Saved[3] = Saved[4] = 0 ;
	Saved[5] = Saved[6] = Saved[7] = Saved[8] = 0 ;

	getline( Line1 ) ;
	getline( Line2 ) ;

	Lastline = Line1 ;
	Thisline = Line2 ;
	Nextline = Line3 ;

	while ( getline( Nextline ) ) {

		/* double EX DE,HL */
		if ( strcmp( "	EX DE,HL",Lastline) == 0 ) {
		  if ( strcmp( "	EX DE,HL",Thisline) == 0 ) {
		    Temp = Lastline ;
		    Lastline = Nextline ;
		    Nextline = Temp ;
		    getline( Thisline ) ;
		    getline( Nextline ) ;
		    ++Saved[0] ;
		  }
		}


		/* DE<-HL followed by EX DE,HL */
		if ( strcmp("	LD D,H",Lastline) == 0 ) {
		  if ( strcmp("	LD E,L",Thisline) == 0 ) {
		    if ( strcmp("	EX DE,HL",Nextline) == 0 ) {
			getline(Nextline) ;
			++Saved[1] ;
		    }
		  }
		}

		/* DE<-HL followed by LD HL */
		if ( strcmp("	LD D,H",Lastline) == 0 ) {
		  if ( strcmp("	LD E,L",Thisline) == 0 ) {
		    if ( match("	LD HL,",Nextline) != 0 ) {
			strcpy( Lastline, "	EX DE,HL" ) ;
			Temp = Thisline ;
			Thisline = Nextline ;
			Nextline = Temp ;
			getline(Nextline) ;
			++Saved[2] ;
		    }
		  }
		}

		/* expand ccpchar */
		if ( strcmp("	CALL ccpchar",Lastline) == 0 ) {
			putline( "	LD A,L" ) ;
			strcpy( Lastline, "	LD (DE),A" ) ;
			++Saved[8] ;
		}

		/* ccgint to DE expanded */
		if ( strcmp("	CALL ccgint",Lastline) == 0 ) {
		  if ( strcmp("	EX DE,HL",Thisline) == 0 ) {
		    if ( match("	LD HL,",Nextline) != NULL ) {
			putline( "	LD E,(HL)" ) ;
			strcpy( Lastline, "	INC HL" ) ;
			strcpy( Thisline, "	LD D,(HL)" ) ;
			++Saved[3] ;
		    }
		  }
		}

		/* expand ccgint */
		if( strcmp("	CALL ccgint", Lastline) == 0 ) {
			putline( "	LD A,(HL)" ) ;
			putline( "	INC HL" ) ;
			putline( "	LD H,(HL)" ) ;
			strcpy( Lastline, "	LD L,A" ) ;
			--Saved[4] ;
		}

		/* expand ccpint */
		if ( strcmp( "	CALL ccpint", Lastline) == 0 ) {
			putline( "	LD A,L" ) ;
			putline( "	LD (DE),A" ) ;
			putline( "	INC DE " ) ;
			putline( "	LD A,H" ) ;
			strcpy( Lastline, "	LD (DE),A" ) ;
			--Saved[5] ;
		}

		/* expand ccgchar to DE */
		if ( strcmp("	CALL ccgchar",Lastline) == 0 ) {
		  if ( strcmp("	EX DE,HL",Thisline) == 0 ) {
		    if ( match("	LD HL,",Nextline) != NULL ) {
			putline( "	LD A,(HL)" ) ;
			putline( "	LD E,A" ) ;
			putline( "	RLCA" ) ;
			strcpy( Lastline, "	SBC A,A" ) ;
			strcpy( Thisline, "	LD D,A" ) ;
			--Saved[6] ;
		    }
		  }
		}

		/* expand ccgchar */
		if ( strcmp("	CALL ccgchar",Lastline) == 0 ) {
			putline( "	LD A,(HL)" ) ;
			putline( "	LD L,A" ) ;
			putline( "	RLCA" ) ;
			putline( "	SBC A,A" ) ;
			strcpy( Lastline, "	LD H,A" ) ;
			--Saved[7] ;
		}

		putline( Lastline ) ;
		Temp = Lastline ;
		Lastline = Thisline ;
		Thisline = Nextline ;
		Nextline = Temp ;
	}
	putline( Lastline ) ;
	putline( Thisline ) ;

	puts("Double EX DE,HL:              "); putdec(Saved[0]) ; crlf() ;
	puts("DE<-HL, followed by EX DE,HL: "); putdec(Saved[1]) ; crlf() ;
	puts("DE<-HL, followed by LD HL:    "); putdec(Saved[2]) ; crlf() ;
	puts("Expand ccpchar:               "); putdec(Saved[8]) ; crlf() ;
	puts("Expand ccgint to DE:          "); putdec(Saved[3]) ; crlf() ;
	puts("Expand ccgint:                "); putdec(Saved[4]) ; crlf() ;
	puts("Expand ccpint:                "); putdec(Saved[5]) ; crlf() ;
	puts("Expand ccgchar to DE:         "); putdec(Saved[6]) ; crlf() ;
	puts("Expand ccgchar:               "); putdec(Saved[7]) ; crlf() ;
	crlf() ;
	i = Saved[0]*2 + Saved[1] + Saved[2] + Saved[3] + Saved[4] ;
	i = i + Saved[5]*2  + Saved[6] + Saved[7]*2 + Saved[8] ;
	puts("Total bytes saved: ") ; putdec( i ) ; crlf() ;

	fclose( Ichan ) ;
	fclose( Ochan ) ;
}

/*
 * islower - returns TRUE if character is lower case, else FALSE
 */
islower( ch )
char ch ;
{
	return (ch >= 'a') & (ch <= 'z') ;
}

/*
 * uncomma - split string with two tokens separated by commas
 */
uncomma( string, token1, token2 )
char *string, *token1, *token2 ;
{
	while ( *string != ',' )
		*token1++ = *string++ ;
	*token1 = 0 ;
	++string ;
	strcpy( token2, string ) ;
}

pass3()
{
	char source1[20], source2[20], dest1[20], dest2[20] ;
	char *tail2 ;
	int i ;

	Saved[0] = Saved[1] = Saved[2] = Saved[3] = Saved[4] = 0 ;
	Saved[5] = Saved[6] = 0 ;

	getline( Line1 ) ;
	getline( Line2 ) ;

	Lastline = Line1 ;
	Thisline = Line2 ;
	Nextline = Line3 ;

	while ( getline( Nextline ) ) {


		/* ld hl, ex, ld hl -> ld de, ld hl */
		if ( (Tail=match("	LD HL,",Lastline)) != NULL ) {
		  if ( strcmp("	EX DE,HL",Thisline) == 0 ) {
		    if ( match("	LD HL,",Nextline) != NULL ) {
			if ( *Tail != '(' ) {
				strcpy( Line, "	LD DE," ) ;
				strcat( Line, Tail ) ;
				strcpy( Lastline, Line ) ;
				Temp = Thisline ;
				Thisline = Nextline ;
				Nextline = Temp ;
				getline( Nextline ) ;
				++Saved[0] ;
			}
		    }
		  }
		}

		/* jump on zero round unconditional jump */
		if ( (Tail=match("	JP Z,",Lastline)) != NULL ) {
		  if ( match(Tail,Nextline) != NULL ) {
		    if ( (Tail=match("	JP ",Thisline)) != NULL ) {
		      if ( islower( *Tail ) ) {
				strcpy( Line, "	JP NZ," ) ;
				strcat( Line, Tail ) ;
				strcpy( Lastline, Line ) ;
				Temp = Thisline ;
				Thisline = Nextline ;
				Nextline = Temp ;
				getline( Nextline ) ;
				++Saved[1] ;
		      }
		    }
		  }
		}

		/* store followed by load */
		if ( (Tail=match("	LD ",Thisline)) != NULL ) {
		  if ( (tail2=match("	LD ",Nextline)) != NULL ) {
			uncomma( Tail, dest1, source1 ) ;
			uncomma( tail2, dest2, source2 ) ;
			if ( strcmp(dest1,source2) == 0 ) {
			  if ( strcmp(dest2,source1) == 0 ) {
				getline( Nextline ) ;
				++Saved[2] ;
			  }
			}
		  }
		}


		putline( Lastline ) ;
		Temp = Lastline ;
		Lastline = Thisline ;
		Thisline = Nextline ;
		Nextline = Temp ;
	}
	putline( Lastline ) ;
	putline( Thisline ) ;

	puts("LD HL;EX;LD HL > LD DE;LD HL: "); putdec(Saved[0]) ; crlf() ;
	puts("JP Z round JP:                "); putdec(Saved[1]) ; crlf() ;
	puts("Store followed by load:       "); putdec(Saved[2]) ; crlf() ;
	crlf() ;
	i = Saved[0] + Saved[1]*3 + Saved[2]*2 ;
	puts("Total bytes saved: ") ; putdec( i ) ; crlf() ;

	fclose( Ichan ) ;
	fclose( Ochan ) ;
}
