// ADBI, [A]DBI [D]ynamic Code Generating [B]F [I]nterpreter.
// Extra Credit Project for CS 333
// Chris Frost <chris@frostnet.net>

// Use: The sole program argument is used as the input BF filename

#include <stdio.h>
#include <stdlib.h>
#include <lightning.h> // Developed using Lightning v0.97

// M is the memory accesible to BF programs, I stores the BF instructions
#define M_size 30000 // Size defined by BF spec
#define I_size 10000 // Size is up to the user
unsigned char M[M_size], I[I_size];
unsigned int DC = 0; // Data Counter
static jit_insn codeBuffer[50*I_size]; // 50*I_size is just an empirical guess

typedef void (*pvfv)(void);

jit_insn *loop_stack[I_size];     // Store jit_insn* for implementing loops
unsigned int ls_i = 0;            // Current item location in loop_stack
void ls_push(jit_insn *location); // push an item into loop_stack
jit_insn *ls_pop();               // pop an item from loop_stack

pvfv compile_bf(char *expr); // Compile code beginning at char expr until
                             // reach NULL char


int main(int argc, char *argv[])
{
	FILE *file;
	unsigned char bf_inst;
	unsigned int i = 0;
	pvfv bf;
	
   // Load BF source
   file = fopen(argv[1], "r");
   if(!file) {
      printf("Could not open input file \"%s\", exiting.\n", argv[1]);
      exit(1);
   }
   
   while( ((bf_inst=getc(file)) != 255) && (i < I_size)) {
      if ('\n' != bf_inst)
         I[i++] = bf_inst;
   }
	fclose(file);
   if(i >= I_size) {
      printf("BF program too long for instruction array, exiting.\n");
      exit(1);
   }
   I[i] = 0;


	// Compile and execute the BF code
	jit_set_ip(codeBuffer);
	bf = compile_bf(I);
	jit_flush_code(codeBuffer, jit_get_ip().ptr);
	
	bf();

#ifdef DEBUG
	printf("\n Memory dump:\n");
	for(i = 0; i < M_size; i++)
		printf("%u ", M[i]);
#endif

	return 0;
}


pvfv compile_bf(char *expr)
{
	pvfv fn;
	jit_insn *insn_jump;

	if (NULL == expr) {
		printf("ERROR: compile_bf() was passed a NULL pointer, exiting.\n");
		exit(1);
	}
	
	fn = (pvfv)(jit_get_ip().iptr);
	jit_prolog(0);

	while(*expr) {
		// Useful values, refreshed each loop so that they can be written over
		jit_movi_p(JIT_V2, &DC);
		jit_ldr_p(JIT_R2, JIT_V2); // R2 = DC
		jit_movi_p(JIT_R1, M);     // R1 = M
		
		if ('>' == *expr) {
			jit_addi_p(JIT_R2, JIT_R2, 1);
			jit_movi_p(JIT_R0, &DC);
			jit_str_p(JIT_R0, JIT_R2);
		}
		else if ('<' == *expr) {
			jit_subi_p(JIT_R2, JIT_R2, 1);
			jit_movi_p(JIT_R0, &DC);
			jit_str_p(JIT_R0, JIT_R2);
		}
		else if ('+' == *expr) {
			jit_ldxr_uc(JIT_R0, JIT_R1, JIT_R2);
			jit_addi_ui(JIT_R0, JIT_R0, 1);
			jit_stxr_uc(JIT_R1, JIT_R2, JIT_R0);
		}
		else if ('-' == *expr) {
			jit_ldxr_uc(JIT_R0, JIT_R1, JIT_R2);
			jit_subi_ui(JIT_R0, JIT_R0, 1);
			jit_stxr_uc(JIT_R1, JIT_R2, JIT_R0);
		}
		else if ('.' == *expr) {
			jit_ldxr_uc(JIT_R0, JIT_R1, JIT_R2);
			jit_movi_p(JIT_V0, "%c");
			jit_prepare(2);
			jit_pusharg_i(JIT_R0);
			jit_pusharg_p(JIT_V0);
			jit_finish(printf);
		}
		else if (',' == *expr) {
			jit_calli(getchar);
			jit_retval(JIT_R0);

			jit_ldr_p(JIT_R2, JIT_V2); // R2 = DC
			jit_movi_p(JIT_R1, M);     // R1 = M
			jit_stxr_uc(JIT_R1, JIT_R2, JIT_R0);			
		}
		else if ('[' == *expr) {
			ls_push(jit_get_label());		

			jit_ldxr_uc(JIT_R0, JIT_R1, JIT_R2);
			ls_push(jit_beqi_ui(jit_forward(), JIT_R0, 0));
		}
		else if (']' == *expr) {
			insn_jump = ls_pop();
			// If we got here going through the loop, go back to beginning
			jit_jmpi(ls_pop());
			
			// If we are done with the loop, continue on from this point
			jit_patch(insn_jump);
		}
		else {
			printf("Encountered illegal BF instruction \'%c\', ignoring.\n",
					 *expr);
		}
		
		expr += sizeof(*expr);
	}	

	jit_ret();
	return fn;
}


void ls_push(jit_insn *location)
{
	loop_stack[ls_i++] = location;
}


jit_insn *ls_pop()
{
	return loop_stack[--ls_i];
}


// Alternative version of compile_bf() which keeps M[DC] in V2 instead of
// reading memory for every BF instruction. However, there is /something/
// slightly wrong with this implementation and I don't quite know what it is.
// (If you change the code so that you always read from/write to memory in
// addition to register V2 it works, but then of course we're back to what
// the above function does.)
#if 0
pvfv compile_bf(char *expr)
{
	pvfv fn;
	jit_insn *insn_jump;

	if (NULL == expr) {
		printf("ERROR: compile_bf() was passed a NULL pointer, exiting.\n");
		exit(1);
	}
	
	fn = (pvfv)(jit_get_ip().iptr);
	jit_prolog(0);

	// V2 = M[DC]
	jit_movi_p(JIT_R0, &DC);
	jit_ldr_p(JIT_R1, JIT_R0);
	jit_movi_p(JIT_R0, M);
	jit_ldxr_uc(JIT_V2, JIT_R0, JIT_R1);

	while(*expr) {
		if ('>'==*expr || '<'==*expr) {
			jit_movi_p(JIT_R0, &DC);
			jit_ldr_ui(JIT_R1, JIT_R0);
			jit_movi_p(JIT_R2, M);

			// Save current data
			jit_stxr_uc(JIT_R1, JIT_R2, JIT_V2);

			// Increment/Decrement DC
			if ('>' == *expr)
				jit_addi_ui(JIT_R1, JIT_R1, 1);
			else
				jit_subi_ui(JIT_R1, JIT_R1, 1);

			jit_str_ui(JIT_R0, JIT_R1);
			
			// Read new data
			jit_ldxr_uc(JIT_V2, JIT_R1, JIT_R2);
		}
		else if ('+' == *expr) {
			jit_addi_ui(JIT_V2, JIT_V2, 1);
		}
		else if ('-' == *expr) {
			jit_subi_ui(JIT_V2, JIT_V2, 1);
		}
		else if ('.' == *expr) {
			jit_movi_p(JIT_R0, "%c");
			jit_prepare(2);
			jit_pusharg_i(JIT_V2);
			jit_pusharg_p(JIT_R0);
			jit_finish(printf);
		}
		else if (',' == *expr) {
			// Input three character string
         // (If used, there nees to be a global variable "char input[4]")
			// TODO: change to input a byte rather than numerals
			// V0 = scanf("%c", input)
			jit_movi_p(JIT_V0, input);
			jit_movi_p(JIT_R0, "%3c");
			jit_prepare(2);
			jit_pusharg_p(JIT_V0);
			jit_pusharg_p(JIT_R0);
			jit_finish(scanf);
			
			// V2 = atoi(input)
			jit_prepare(1);
			jit_pusharg_p(JIT_V0);
			jit_finish(atoi);
			jit_retval(JIT_V2);
		}
		else if ('[' == *expr) {
			ls_push(jit_get_label());		
			
			ls_push(jit_beqi_ui(jit_forward(), JIT_V2, 0));
		}
		else if (']' == *expr) {
			insn_jump = ls_pop();
			
			// If we got here going through the loop, go back to beginning
			jit_jmpi(ls_pop());
			
			// If we are done with the loop, continue on from this point
			jit_patch(insn_jump);
			
		}
		else {
			printf("Encountered illegal BF instruction \'%c\', ignoring.\n",
					 *expr);
		}
		
		++expr; //expr += sizeof(*expr);
	}	

	jit_ret();
	return fn;
}
#endif
