/* codegen.c */
#include "codegen.h"
#include "ctype.h"
#include "error.h"
#include "expression.h"
#include "lex.h"
#include "parse.h"
#include "stdlib.h"
#include "string.h"
#include "vars.h"

/* 1 - Print specified number as label */
void printlabel(int label)
{
	/**	outasm("cc"); **/
	outasm("L_");
	outudec(label);
}

/** 2 - Print specified number as label **/
/** printlabel(flab1);colon();nl();     **/
void sa_mk_lbl(int n)
{
	outasm("L_");
	outudec(n);
	colon();
	nl();
}

/* Print specified number as label for stack */
void ostklbl()
{
	/**	outasm("cs");  **/
	
	/** auto constant **/
	outasm("AutoC");
	outudec(litlab);
}

/* Write text destined for the assembler to read (i.e. stuff not in comments) */
void outasm(char* ptr)
{
	/**	while(foutput_chr(toupperi(*ptr++))); **/
	while (foutput_chr(*ptr++))
		;
}

void nl()
{
	foutput_chr(EOL);
}

void tab()
{
	foutput_chr(9);
}

void colon()
{
	foutput_chr(58);
}

void bell()
{
	foutput_chr(7);
}

void ol(char* ptr)
{
	ot(ptr);
	nl();
}

void ot(char* ptr)
{
	tab();
	outasm(ptr);
}

/* Output a signed number to the ASM file. */
void outdec(int number)
{
	int k, zs;
	char c;

	zs = 0;
	k = 10000;
	if (number<0)
	{
		number = (-number);
		foutput_chr('-');
	}
	else
		foutput_chr('+');
	while (k >= 1)
	{
		c = number / k + '0';
		if ((c != '0') | (k == 1) | (zs))
		{
			zs = 1;
			foutput_chr(c);
		}
		number = number % k;
		k = k / 10;
	}
}

/* Output an unsigned number to ASM file. */
void outudec(int number)
{
	int k,zs;
	char c;

	zs = 0;
	k = 10000;
	if (number<0)
		number = (-number);
	while (k >= 1)
	{
		c = number / k + '0';
		if ((c != '0') | (k == 1) | (zs))
		{
			zs = 1;
			foutput_chr(c);
		}
		number = number % k;
		k = k / 10;
	}
}

/*
	3 element evaluation array of ints -
		eval[0] = Symbol table address, else 0 for constant
		eval[1] = type indirect object to fetch, else 0 for static or const obj
		eval[2] = type pointer or array, else 0
*/
void sa_store(int* eval)
{
	/** If is const **/
	if (eval[1] == 0)
		sa_putmem((char*)eval[0]);
	else
		sa_putstk(eval[1]);
}

/** eval[1] holds typeobj, char or int, 0 for const. **/
/** This is to Retrieve Value, from auto or static **/
/** Syntactic Action for Dereferencing Symbol Value **/
/** Ren rvalue -> rq_val_sa                         **/
void rq_val_sa(int* eval)
{
	/** If not a const & indirect obj **/
	if ((eval[0] != 0) & (eval[1] == 0))
		sa_getmem((char*)eval[0]);
	else  /** const or static **/
		sa_indirect(eval[1]);
}

/* True if val1 -> int pointer or int array and val2 not ptr or array */
int dbltest(int val1[], int val2[])
{
	if (val1[2] != CINT)
		return 0;
	
	if (val2[2])
		return 0;
	
	return 1;
}

/*
	3 element evaluation array of ints -
		eval[0] = Symbol table address, else 0 for constant
		eval[1] = type indirect object to fetch, else 0 for static object (const.)
		eval[2] = type pointer or array, else 0
*/
/*** determine type of binary operation ***/
void result(int eval[], int eval2[])
{
	/* If both are type ptr or array.*/
	if (eval[2] & eval2[2])
	{
		eval[2] = 0;
	}
	/*eval2 is type ptr or array. */
	else if (eval2[2])
	{
		eval[0] = eval2[0];
		eval[1] = eval2[1];
		eval[2] = eval2[2];
	}
}

/** boolean conditional test - jmp - if (t) **/
/** !superceded! **/
/**** logical_test(label) int label;
{
	needbrack("(");
	expression();
	needbrack(")");
	sa_tst_jump(label); ** gen syntactic action **
}  ****/

/** Ver_2 boolean conditional test - jmp - if (tf) **/
/**** -- abandoned --
logical_tst2(label,tf) int label, tf;
{
	needbrack("(");
	expression();      ** ref expr2(YES); for comma'd **
	needbrack(")");
	sa_tst2_jmp(label,tf); ** gen syntactic action **
}  ****/

void logic_tst(int t_lbl, int flab1)
{
	needbrack("(");
	expression(YES);
	needbrack(")");
	sa_if_tst(t_lbl,flab1);
}

/** new, for DO **/
void logic_do_tst(int t_lbl, int flab1)
{
	needbrack("(");
	expression(YES);
	needbrack(")");
	/** true label, false=exit label: **/
	sa_do_tst(t_lbl,flab1);
}

/** parameter is a ptr, *val **/
int kind_const(int val[])
{
	if (number(val))
		sa_immed();
	else if (chr_const(val))
		sa_immed();
	else if (str_const(val))
	{
		/* global & non-array & function */
		sa_immedo();
		printlabel(litlab);
	}
	else
		return 0;
	
	outdec(val[0]);
	nl();
	
	return 1;
}

int number(int val[])
{
	int k, minus;
	char c;

	k = minus = 1;
	
	while (k)
	{
		k = 0;
		
		if (match("+"))
			k = 1;
		
		if (match("-"))
		{
			minus = (-minus);
			k = 1;
		}
	}

	if (numeric(inspect_chr()) == 0)
		return 0;
	
	while (numeric(inspect_chr()))
	{
		c = scan_nxt();
		k = k * 10 + (c - '0');
	}
	
	if (minus < 0)
		k = (-k);
	
	val[0] = k;
	
	return 1;
}

int chr_const(int val[])
{
	int k;
	char c;

	k = 0;
	
	if (match("'") == 0)
		return 0;
	
	/** 39 is single quote **/
	while ((c = g_nxtchr()) != 39)
		k = (k & 255) * 256 + (c & 127);
	
	val[0] = k;
	
	return 1;
}

/** Quoted string entered into literals table **/
int str_const(int val[])
{
	/** quoted string entered into Literal Str Table **/
	char c;

	if (match(quote) == 0)
		return 0;
	
	val[0] = litptr;
	
	while (inspect_chr() != '"')
	{
		if (inspect_chr() == 0)
			break;
		
		if (litptr >= LITMAX)
		{
			/** cleanup literal error **/
			error("string space exhausted");
			
			while (match(quote) == 0)
				if (g_nxtchr() == 0)
					break;
			
			return 1;
		}
		
		/** copy to literal table: **/
		litq[litptr++] = g_nxtchr();
	}
	
	g_nxtchr();
	litq[litptr++] = 0;
	
	return 1;
}

/*	>>>>>> start of cc8 <<<<<<<	*/
/* Begin a comment line for the assembler */
void sa_comment()
{
	foutput_chr(';');
}

/* Put out assembler info before any code is generated */
void header()
{
	/** sa_comment();nl(); **/
	if(mainflg)
	{
		/* do stuff needed for first */
		ol(";; ----===*******===----");
		/** NASM form: **/ 
		ol("%include 'asm/prolog.asm' ");
		outstr(";; --== NASM vers. 12-Apr-2012 ==--");
		nl();
		ol(";; ***---- Program Body ----***");
		ol("  [SECTION .text]");
	}
}

/* Print any assembler stuff needed after all code */
void trailer()
{
	nl();
	/** NASM form: **/
	ol("%include 'asm/epilog.asm' ");
	sa_comment();
	outstr(" --- End of Compilation ---");
	nl();
}

/* Continue code segment stuff. */
void sa_cseg()
{
	nl();
	/* Start Code Segment from last point: */
	ol("  [SECTION .text]");
}

/* Continue data segment stuff. */
void sa_dseg()
{
	nl();
	/* Start Data Segment from last point: */
	ol("  [SECTION .data]");
}

/* Function initialization. */
void FN_Prolog()
{
	/* Save old frame ptr: */
	ol("PUSH EBP");
	
	/* Adjust stack beyond new frame: */
	ot("SUB  ESP, ");
	
	ostklbl();
	nl();
	
	/* Get new frame ptr: */
	ol("MOV  EBP, ESP");
	nl();
}

/* Function termination. */
void FN_Epilog()
{
	nl();
	/* Generate frame size: */
	ostklbl();
	ot("EQU ");
	outudec(Msp);
	nl();
}

/* Print out a name such that it won't annoy the assembler */
/*	(by matching anything reserved, like opcodes.) */
/** chgd outname() -> asm_symb(), removed leading "cz" **/
/** removed toupperi, nasm is case sensitive. **/

/* CHG(a).. Sun 14 Aug 2011 02:44:41 PM CDT added back leading chr
** to symbols generated for nasm so that the io_clib extern labels
** are not duplicates. ie..
** CHG: fclose(); -> fclose: -> call fclose ;extern clib_fn
** TO:  fclose(); -> _fclose: -> call fclose ;extern clib_fn
*/
void asm_symb(char* sname)
{
	int len, i, j;

	/** outasm("cz"); **/
	/** CHG(a): **/
	outasm("_");
	len = strlen(sname);
	
	if (len > (asmpref + asmsuff))
	{
		i = asmpref;
		len = len - asmpref - asmsuff;
		
		/** no case conversion version: **/
		while (i-- > 0)
			foutput_chr(*sname++);
		
		while (len-- > 0)
			sname++;
		
		while (*sname)
			foutput_chr(*sname++);

/****
		while(i-- > 0)
			foutput_chr(toupperi(*sname++));
		while(len-- > 0)
			sname++;
		while(*sname)
			foutput_chr(toupperi(*sname++));
****/
	}
	else
		outasm(sname);
}

/* Fetch a static memory cell into the primary register */
void sa_getmem(char* sym)
{
	if ((sym[IDENT] != POINTER) & (sym[TYPE] == CCHAR))
	{
		ot("MOV  AL, [Byte ");
		asm_symb(sym + NAME);
		/**	ot("]"); **/
		outasm("]");
		nl();
		ol("CBW");
		ol("MOV  EBX, EAX");
		nl();
	}
	else
	{
		ot("MOV  EBX, [DWord ");
		asm_symb(sym + NAME);
		/**	ot("]"); **/
		outasm("]");
		nl();
	}
}

/*
	Fetch the stack relative address of the specified
	automatic symbol into the primary register
*/
void sa_deref(char* sym)
{
	ot("LEA  EBX, [");
	ostklbl();
	
	outdec( (sym[OFFSET] & 255) + 
			((sym[OFFSET+1] & 255) << 8) +
			((sym[OFFSET+2] & 255) << 16) +
			((sym[OFFSET+3] & 255) << 24) );

	outasm("+EBP]");
	nl();
}

/* Store the primary register into the specified static memory cell. */
void sa_putmem(char* sym)
{
	if ((sym[IDENT] != POINTER) & (sym[TYPE] == CCHAR))
	{
		ot("MOV  Byte [");
		asm_symb(sym+NAME);
		/**	ot("], BL");  **/
		outasm("], BL");
		nl();
	}
	else
	{
		ot("MOV  DWord [");
		asm_symb(sym+NAME);
		/**	ot("], BX");  **/
		outasm("], EBX");
		nl();
	}
}

/* Store the specified object type in the primary register */
/* at the address on the top of the stack. */
/** presumes DS:EDI is address of target **/
void sa_putstk(char typeobj)
{
	ol("POP  EDI");
	Csp = Csp + INT_SZ;
	if (typeobj == CCHAR)
	   ol("MOV  Byte [EDI], BL");
	else
	   ol("MOV  DWord [EDI], EBX");
}

/* Fetch the specified object type indirect through the */
/*	primary register into the primary register */
/** called as: indirect(eval[1], eval[0]);  **/
/** these are auto class on stack **/
/*** reverted, problem with array[0] is DS relative ***/
void sa_indirect(char typeobj)
{
	if (typeobj == CCHAR)
	{
		/**	ol("MOV  AL, [SS:BX]"); **/
		ol("MOV  AL, [EBX]");
		ol("CBW");
		ol("MOV  EBX, EAX");
	}
/**	else  ol("MOV  BX, [SS:BX]"); **/
	else
		ol("MOV  EBX, [EBX]");
}

/* Swap the primary and secondary registers */
void sa_swap()
{
	ol("XCHG EBX, EDX");
}

/* Print partial instruction to get an immediate value */
/*	into the primary register */
void sa_immed()
{
	ot("MOV  EBX, ");
}

/* Print partial instruction to get an immediate value into the primary register. */
/* global & non-array & function */
void sa_immedo()
{
	ot("MOV  EBX, ");
}

/* Push the primary register onto the stack. */
void sa_push()
{
	ol("PUSH EBX");
	Csp = Csp - INT_SZ;
}

/* Pop the top of the stack into the secondary register */
void sa_pop()
{
	ol("POP  EDX");
	Csp = Csp + INT_SZ;
}

/* Swap the primary register and the top of the stack */
/** ck for DS dependency - note, EA can use SS: vs default DS: **/
/*** reverted, problem with array[0] is DS relative ***/
void sa_swapstk()
{
	ol("MOV  EDI, ESP");
	/** small memory model dependent: **/
	ol("XCHG EBX, [EDI]");
/**	ol("XCHG BX, [SS:EDI]");  ** mem model independent **/
}

/** EO C86N-8.C **/
/*	>>>>>> start of cc9 <<<<<<<	*/

/* Call the specified subroutine name */
void sa_call(char* sname)
{
	ot("call ");
	asm_symb(sname);
	nl();
}

/* Call a run-time library routine */
void sa_callrts(char* sname)
{
	ot("call ");
	outasm(sname);
	nl();
}

/* Return from subroutine */
void sa_ret()
{
	ol("ret");
}

/* Perform subroutine call to addr on top of stack */
/*** reverted, problem with array[0] is DS relative ***/
void sa_callstk()
{
	/** new version (TOS holds pointer to target) **/
	ol("POP  EBX");
	ol("CALL EBX");

/**
	old version:
	
	sa_immed();
	outasm("( $+10)");
	nl();
	ol("MOV  EDI, ESP");
	ol("XCHG EBX, [EDI]"); ** small memory model dependent **
*-*	ol("XCHG BX, [SS:EDI]"); ** mem model independent *-*
	ol("JMP  EBX");
**/
	
	Csp=Csp-INT_SZ;
}

/* Jump to specified internal label number */
void sa_jump(int label)
{
	ot("jmp  ");
	printlabel(label);
	nl();
}

/* Test the primary register and jump if false to label */
/** testjump()->sa_tst_jump() **/
/** caller - logical_test(label); - superceded **/
/**** sa_tst_jump(label) int label;
{
	ol("OR   EBX, EBX");
	ol("JNZ  $+5");
	ot("JMP  ");
	printlabel(label);
	nl();
}  ****/

/* ver_2 - Test the primary register and jump if false to label */
/** the supplied condition, tf, dictates which form to codegen **/
/**** caller - logical_tst2(label,tf); !abandoned! --
sa_tst2_jmp(label, tf) int label, tf;
{
	ol("OR   EBX,EBX");
	if (tf)  ot("JNZ  ");
	else     ot("JZ   ");
	printlabel(label);
	nl();
}  ****/

/** ver_3 **/
void sa_if_tst(int t_lbl, int flab1)
{
	ol("OR   EBX, EBX");
	ot("JNZ  ");
	printlabel(t_lbl);
	nl();
	ot("JMP  ");
	printlabel(flab1);
	nl();
}

void sa_do_tst(int t_lbl, int flab1)
{
	/** do .. while specific **/
	ol("OR   EBX, EBX");
	ot("JNZ  ");
	printlabel(t_lbl);
	nl();
	ot("JMP  ");
	printlabel(flab1);
	nl();
	/** codegen true branch label here: **/
	sa_mk_lbl(t_lbl);
}

/** Added, Modified for NASM, caller dumpstatics() **/
void def_sizein()
{
	ot("TIMES "); /**ot("DS ");**/
}

/* Print pseudo-op to define storage (Character). */
/** caller dumpstatics() **/

void defstorchr()
{
	ot("DB 0"); /** RB **/
}

/* Print pseudo-op to define storage (Integer). */
/* Changed to DD */
/** Caller: dumpstatics() **/
void defstorint()
{
	/** RW **/
	ot("DD 0");
}

/* Print pseudo-op to define storage (Pointer). */
/* Changed to DD */
/** Caller: dumpstatics() **/
void defstorptr()
{
	/** RW **/
	ot("DD 0");
}

/* Print pseudo-op to define a byte */
void defbyte()
{
	ot("DB ");
}

/* Print pseudo-op to define a word (changed to DD) */
void defword()
{
	ot("DD ");
}

/* Modify the stack pointer to the new value indicated */
int sa_modstk(int newsp)
{
	int k;

	k = newsp - Csp;
	
	if (k == 0)
		return newsp;
	
	ot("ADD  ESP, ");
	outdec(k);
	nl();
	return newsp;
}

/* Modify the stack pointer to the new value indicated */
int sa_rtnstk(int newsp)
{
	if (Msp != 0)
	{
		/* Remove frame from stack: */
		ot("ADD  ESP, ");
		ostklbl();
		nl();
	}
	ol("POP  EBP");
	return newsp;
}

/* Primary Register = EBX, Secondary Register = EDX */
/* Double the primary register */
void sa_doublereg()
{
	ol("SAL  EBX, 1");
}

/* Add the primary and secondary registers (results in primary) */
void sa_add()
{
	ol("ADD  EBX, EDX");
}

/* Subtract the primary register from the secondary (results in primary) */
void sa_sub()
{
	ol("SUB  EDX, EBX");
	ol("MOV  EBX, EDX");
}

/* Multiply the primary and secondary registers (results in primary) */
/** MUL EAX, r/m32 -> EDX:EAX **/
void sa_mult()
{
	/** Primary: **/
	ol("MOV  EAX, EBX");
	
/**	ol("PUSH EDX");		**/
	
	/** Secondary: **/
	ol("MUL  EDX");
	
/**	ol("POP  EDX");		**/
	
	/** Result: lo dword in primary, hi dword in secondary register for return **/
	ol("MOV  EBX, EAX");
}

/*
	Divide the secondary register by the primary
	(quotient in primary, remainder in secondary)
*/
/** DIV EDX:EAX, r/m32 -> EAX = Quo., EDX = Rem. **/
void sa_div()
{
	ol("MOV  EAX, EDX");
	/** signed EAX -> qword, EDX:EAX **/
	ol("CDQ");
	/** signed div by primary **/
	ol("IDIV  EBX");
	/** EAX Quo., EDX signed Rem. returns Quo. in Primary, signed Rem. in Secondary. **/
	ol("MOV  EBX, EAX");
}

/* Compute remainder (modulo) of secondary register divided by the primary */
/* (remainder in primary, quotient in secondary) */
void sa_modulo()
{
	sa_div();
	sa_swap();
}

/* Inclusive 'or' the primary and the secondary registers (results in primary) */
void sa_or()
{
	ol("OR   EBX, EDX");
}

/* Exclusive 'or' the primary and seconday registers (results in primary) */
void sa_xor()
{
	ol("XOR  EBX, EDX");
}

/* 'And' the primary and secondary registers (results in primary) */
void sa_and()
{
	ol("AND  EBX, EDX");
}

/*
	Arithmetic shift right the secondary register by
	the count in the primary (results in primary).
*/
void sa_asr()
{
	ol("MOV  ECX, EBX");
	ol("SAR  EDX, CL");
	ol("MOV  EBX, EDX");
}

/*
	Arithmetic left shift the secondary register by the count in the primary.
	(results in primary)
*/
void sa_asl()
{
	ol("MOV  ECX, EBX");
	ol("SAL  EDX, CL");
	ol("MOV  EBX, EDX");
}

/* Form two's complement of primary register */
void sa_neg()
{
	ol("NEG  EBX");
}

/* Form one's complement of primary register */
void sa_not()
{
	ol("NOT  EBX");
}

/* Increment the primary register by one */
void sa_inc()
{
	ol("INC  EBX");
}

/* Decrement the primary register by one */
void sa_dec()
{
	ol("DEC  EBX");
}

/* Following are the conditional operators */
/* They compare the secondary register against the primary */
/* and put a literal 1 in the primary if the condition is */
/* true, otherwise they clear the primary register */

/* Test for equal */
void sa_eq()
{
	sa_callrts("cceq");
}

/* Test for not equal */
void sa_not_equal()
{
	sa_callrts("ccne");
}

/* Test for less than (signed) */
void sa_sless_than()
{
	sa_callrts("cclt");
}

/* Test for less than or equal to (signed) */
void sa_sless_or_eq()
{
	sa_callrts("ccle");
}

/* Test for greater than (signed) */
void sa_sgreater_than()
{
	sa_callrts("ccgt");
}

/* Test for greater than or equal to (signed) */
void sa_sge()
{
	sa_callrts("ccge");
}

/* Test for less than (unsigned) */
void sa_uless_than()
{
	sa_callrts("ccult");
}

/* Test for less than or equal to (unsigned) */
void sa_uless_oreq()
{
	sa_callrts("ccule");
}

/* Test for greater than (unsigned) */
void sa_ugt()
{
	sa_callrts("ccugt");
}

/* Test for greater than or equal to (unsigned) */
void sa_uge()
{
	sa_callrts("ccuge");
}

/** Convert primary value into logical (boolean) value, **/
/** that is, (0 if 0, 1 otherwise) **/
void sa_mk_bool()
{
	int t_lbl;
	
	/** make a unique label for our use: **/
	t_lbl = getlabel();
	ol("AND  EBX, 0FFFFFFFFh");
	
	/** already zero. **/
	ot("JZ   ");
	printlabel(t_lbl);
	nl();
	
	/** .else. > 0, set to one. **/
	ol("MOV  EBX, 1");
	
	/** codegen true branch label here **/
	sa_mk_lbl(t_lbl);
}

/** Convert primary value into logical not (boolean) value, **/
/** that is, (1 if 0, 0 otherwise) **/
void sa_mk_nbool()
{
	int t_lbl, f_lbl;
	
	/** make a unique label for our use: **/
	t_lbl = getlabel();
	f_lbl = getlabel();

	ol("AND  EBX, 0FFFFFFFFh");
	
	/** jz means already zero. **/
	ot("JNZ  ");
	printlabel(t_lbl);
	nl();
	
	/** .else. zero, invert to one. **/
	ol("MOV  EBX, 1");
	ot("JMP  ");
	printlabel(f_lbl);
	nl();
	
	/** codegen true branch label here **/
	sa_mk_lbl(t_lbl);
	
	/** invert to zero **/
	ol("MOV  EBX, 0");
	sa_mk_lbl(f_lbl);
}