#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "mfile.h"
#include "comp.h"
#include "tokedefs.h"
#include "error.h"
#include "cxe.h"
#include "chip.h"
#include "opdef.h"
#include "sym.h"
#include "log.h"
#include "exp.h"

/*
add (compile time) expressions (ie: MOV A, [1024*12234]
 any [...] statement will be replaced with
 the final value.

ADDL %AAUS1, %AAUS2

also, add a pre-processor...
*/

#define rewind() do{ mem_fseek(cur_input->file,-2,SEEK_CUR); curch=mem_getc(cur_input->file); }while(0)
#define rewind_ex(n) do{ mem_fseek(cur_input->file,-##(n),SEEK_CUR); curch=mem_getc(cur_input->file); }while(0)
#define fpos (mem_ftell(cur_input->file)-1)

#define MAX_INIT_KEYWORDS 5
#define MAX_CODE_KEYWORDS 44
#define MAX_DATA_KEYWORDS 3
#define MAX_SYMS     15


int keywords_valid=1;
int file_count=0;
int finished=0;
int error_flag=0;
int ctok = TOK_UNKNOWN;
int ctok_line = 0;
char expect=0;
char curch='\0';

token *tok;
inputfile *cur_input;
inputfile filelist[MAX_DEPTH];

/* initial keyword table */
charsym initialkeywords[MAX_INIT_KEYWORDS] = {
	{ "DATA",    TOK_DATA,    },
	{ "CODE",    TOK_CODE,    },
	{ "IMPORT",  TOK_IMPORT,  },
	{ "INCLUDE", TOK_INCLUDE, },
	{ { 0 },     TOK_UNKNOWN, },
};

charsym datakeywords[MAX_DATA_KEYWORDS] = {
	{ "DB",      TOK_DB,      },
	{ "DW",      TOK_DW,      },
	{ { 0 },     TOK_UNKNOWN, },
};

/* keyword table */
charsym codekeywords[MAX_CODE_KEYWORDS] = {
	{ "ADD",     TOK_ADD,     },
	{ "AND",     TOK_AND,     },
	{ "CALL",    TOK_CALL,    },
	{ "CALLEX",  TOK_CALLEX,  },
	{ "CMP",     TOK_CMP,     },
	{ "DEC",     TOK_DEC,     },
	{ "DIV",     TOK_DIV,     },
	{ "DUMP",    TOK_DUMP,    },
	{ "ENTRY",   TOK_ENTRY,   },
	{ "FUNC",    TOK_FUNC,    },
	{ "GET",     TOK_GET,     },
	{ "HLT",     TOK_HLT,     },
	{ "INC",     TOK_INC,     },
	{ "INT",     TOK_INT,     },
	{ "JE",      TOK_JE,      },
	{ "JG",      TOK_JG,      },
	{ "JGE",     TOK_JGE,     },
	{ "JL",      TOK_JL,      },
	{ "JLE",     TOK_JLE,     },
	{ "JMP",     TOK_JMP,     },
	{ "JNE",     TOK_JNE,     },
	{ "LAND",    TOK_LAND,    },
	{ "LOOP",    TOK_LOOP,    },
	{ "LOR",     TOK_LOR,     },
	{ "MEM",     TOK_MEM,     },
	{ "MOD",     TOK_MOD,     },
	{ "MOV",     TOK_MOV,     },
	{ "MUL",     TOK_MUL,     },
	{ "NOOP",    TOK_NOOP,    },
	{ "OR",      TOK_OR,      },
	{ "POP",     TOK_POP,     },
	{ "POP0",    TOK_POP0,    },
	{ "PUSH",    TOK_PUSH,    },
	{ "PUT",     TOK_PUT,     },
	{ "REF",     TOK_REF,     },
	{ "RET",     TOK_RET,     },
	{ "SHL",     TOK_SHL,     },
	{ "SHR",     TOK_SHR,     },
	{ "SP",      TOK_SP,      },
	{ "ST",      TOK_ST,      },
	{ "SUB",     TOK_SUB,     },
	{ "XCHG",    TOK_XCHG,    },
	{ "XOR",     TOK_XOR,     },
	{ { 0 },     TOK_UNKNOWN, },
};

/* single char symbol table */

charsym validsym[MAX_SYMS] = {
	{ { '\n', 0 }, TOK_NL       },
	{ "$",         TOK_ARG      },
   { "'",         TOK_SQUOTE   },
   { "(",         TOK_LPAREN   },
   { ")",         TOK_RPAREN   },
   { "*",         TOK_MUL      },
   { "+",         TOK_ADD      },
   { ",",         TOK_COMMA    },
   { "-",         TOK_SUB      },
   { "/",         TOK_DIV      },
   { ":",         TOK_COLON    },
   { ";",         TOK_SEMI     },
   { "[",         TOK_LBRACKET },
   { "]",         TOK_RBRACKET },
   {{ 0 },			TOK_UNKNOWN  },
};




void _gettok(int,char *);

int _bsrch_helper(const void *c1, const void *c2)
{
   const char *s1 = c1;
   const charsym *s2 = c2;

   return strcasecmp(s1,s2->str);
}

int _resize_tok_str(int len)
{
   if(tok) free(tok);
   if(len<sizeof(token)) len = sizeof(token); 
   tok = calloc(1,len);
   if(!tok) error("Insuficient memory");
   return 1;
}

int ffwd(void)
{
   curch = mem_getc(cur_input->file);

   if(eq('\n')) { cur_input->linenum++; }
   if(eq('/')) {
      curch = mem_getc(cur_input->file);
      if(eq('/')) {
         while(!eq('\n') && !eq(EOF)) curch=mem_getc(cur_input->file);
         cur_input->linenum++;
      } else rewind();
   }

   if(file_count>1 && mem_feof(cur_input->file)&&eq(EOF)) {
      free(cur_input->file->data);
      mem_fclose(cur_input->file);
      free(cur_input->name);
      file_count--;
      if(file_count<1) {
         finished = 1;
         curch=EOF;
         return 1;
      }
      cur_input = &filelist[file_count];
      if(mem_feof(cur_input->file)) {
         finished = 1; curch=EOF; log("done!");
         return 1;
      }
      curch = mem_getc(cur_input->file);
   }

   if(mem_feof(cur_input->file)&&eq(EOF)) {
   	finished = 1;
   }

   return 1;
}

int iskeyword(void)
{
   charsym *kw = NULL;

   kw = bsearch(tok->str, validkeywords, MAX_KEYWORDS-1, sizeof(charsym), _bsrch_helper);
   if(kw) {
   	if(!keywords_valid) return TOK_IDENT;
   	return kw->id;
	}
   return TOK_UNKNOWN;
}

int issym(void)
{
   charsym *kw = NULL;

   kw = bsearch(tok->str, validsym, MAX_SYMS-1, sizeof(charsym), _bsrch_helper);
   if(kw) return kw->id;

   return TOK_UNKNOWN;
}

int idornum(void)
{
   if(!isnum(tok->ch)) return TOK_IDENT; // regular text (identifier)
   if(tok->str[1]=='\0') return TOK_NUM;

   if(tok->ch=='0') {
      if(tok->str[1]=='x' || tok->str[1]=='X') {
         return TOK_HEXNUM; // base 16 (hexadesimal) number
      }
   }

   return TOK_NUM;
}

int getnum(void)
{
   int spos=0,epos=0,ret=TOK_UNKNOWN,neg=0;
   char tmp[255];
   char cc=0;

   spos = fpos;
   cc = *((char *)cur_input->file->data+spos+1);
   // get next char without calling ffwd()

	if(eq('-')) { neg = 1; ffwd(); }

   if(!(eq('0') && (cc == 'x' || cc == 'X'))) {
   // if doesn't look like a hex number
      
      while(isnum(curch)) {
      // loop while char is a number.
         ffwd();
         if(finished) error("EOF after '%s'",tok->str);         
      }

		ret = TOK_NUM;
      
      epos = fpos;
   } else { // get hex char
      ffwd();
      ffwd();
      while(isalphanum(curch)) {
         if(!ishexchar(curch)) error("illigal char '%c' in hex number",curch);
         ffwd();
         if(finished) error("EOF after '%s'",tok->str);
      }
      epos = fpos;
      ret = TOK_HEXNUM;
   }

   memset(tmp,0,255);
   memcpy(tmp,cur_input->file->data+spos,epos-spos);

   if(ret == TOK_NUM || ret == TOK_HEXNUM) {
      if(!_resize_tok_str(sizeof(int))) error("out of memory");
      if(ret==TOK_NUM) tok->num = neg?-strtol(tmp,(char **)NULL,10):strtol(tmp,(char **)NULL,10);
      else tok->num = strtoul(tmp,(char **)NULL,16);
      if(errno==ERANGE) {
         //if(tok->num==LONG_MAX) warn("literal integer overflow");
         //else if(tok->num==LONG_MIN) warn("literal integer underflow");
         warn("literal integer out of range");
      }
      ret = TOK_NUM;
   }

   return ret;
}

#define UNESCAPE_STR(ch,num) ({\
   if(ch[num]=='\\') { \
      num++; \
      if(ch[num]=='\0') break;\
      switch(ch[num]) { \
         case 'r': \
            ch[num] = '\r'; \
            break; \
         case 'n': \
            ch[num] = '\n'; \
            break; \
         case 't': \
            ch[num] = '\t'; \
            break; \
         case 'a': \
            ch[num] = '\a'; \
            break; \
         case '0': \
            ch[num] = '\0'; \
            break; \
         default: \
            /* every other character is just put back with out the slash */ \
            if(ch[num]!='"') warn("unknown escape code in string: '%c'",ch[num]); \
            break; \
      } \
   } \
ch[num];})

int get_qqstr()
{
   int spos=0,epos=0;
   int tnum=0,num=0,con=0;
   char *str=NULL;

   if(eq('"')) ffwd();
   spos = fpos;
   while(!eq('"') || con) {
      if(eq('\\')) con=1;
      else if(con) con=0;
      if(finished) error("unterminated string");
      if(curch==EOF) error("unterminated string");
      
      ffwd();
   }
   epos = fpos;

   str = calloc(epos-spos+1,sizeof(char));
   if(!str) error("out of memory");
   if(!_resize_tok_str(epos-spos+1)) error("out of memory");

   memcpy(str,cur_input->file->data+spos,epos-spos);
   
   if(eq('"')) ffwd();

   while(1) {
      if(!str[num]) break;
      tok->str[tnum] = UNESCAPE_STR(str,num);
      tnum++; num++;
   }
   free(str);
   return 1;
}

int get_qstr()
{
   if(eq('\'')) ffwd();
   else warn("expected a '");

   if(!_resize_tok_str(2)) error("out of memory");

   if(eq('\\')) {
      ffwd();
      switch(curch) {
         case 'r':
            tok->ch = '\r';
            break;
         case 'n':
            tok->ch = '\n';
            break;
         case 't':
            tok->ch = '\t';
            break;
         case 'a':
            tok->ch = '\a';
            break;
         case 'b':
         	tok->ch = '\b';
         	break;
         	
         case '0':
            tok->ch = '\0';
            break;
         default:
            /* every other character is just put back with out the slash */
            if(curch!='\'') warn("unknown escape code in string: '%c'",curch);
            tok->ch = curch;
            break;
      }
   } else
      tok->ch = curch;
      
   ffwd();

   if(eq('\'')) {
      ffwd();
      skipwhite();
   } else {
      while(!eq('\'')) ffwd();
      ffwd();
      warn("multi-character character constant");
   }

   return 1;
}

void gettok()
{
   int typ=TOK_UNKNOWN;
   int epos=0,spos=0,fp=0,usigned=0,size=REG_INT,cnt=REG_ONE;
   char cc1 = 0, cc2 = 0;
   ctok_line = cur_input->linenum;
   skipwhite();
   fp = fpos;
   cc1 = *((char *)cur_input->file->data+fp);
   cc2 = *((char *)cur_input->file->data+fp+1);

   if(eq('\'') || eq('"')) {
      if(eq('\'')) {
         get_qstr();
         ctok = TOK_NUM;
      } else {
			get_qqstr();
			ctok = TOK_STR;
      }
   } else
   if(eq('%')) {
   	char tmp[3] = {0,0,0};
		ffwd();
		
		if(islower(curch)) curch = toupper(curch);

		if((curch >= 'A' && curch <= 'F') || curch == 'R') {
			tmp[0] = curch;
			ffwd();
			
			if(tmp[0] == 'R') {
				ctok = TOK_R;
			} else			
			if((curch >= 'A' && curch <= 'F') || (curch >= 'a' && curch <= 'f')) {
				if(islower(curch)) curch = toupper(curch);
				tmp[1] = curch;

				ffwd();

			} else if(tmp[0]) {
				tmp[1] = tmp[0];
				tmp[0] = 'A';

			} else
				error("invalid register found");

			if(curch == 'U') {
				usigned = REG_UNSIGNED;
				ffwd();
			} else
			if(curch == 'S') ffwd();
		
			if(curch == 'I') ffwd();
			else
			if(curch == 'B') { size = REG_BYTE; cnt = REG_FOUR; ffwd(); }
			else
			if(curch == 'W') { size = REG_WORD; cnt = REG_ONE; ffwd(); }

			if(isnum(curch) && curch >= '0' && curch < '4') {
				if(curch == '0') cnt = REG_ONE;
				if(curch == '1') cnt = REG_TWO;
				if(curch == '2') cnt = REG_THREE;
				if(curch == '3') cnt = REG_FOUR;
				ffwd();
			}

			if(size == REG_BYTE && cnt > REG_FOUR) error("Invalid 8bit register");
			else
			if(size == REG_WORD && cnt > REG_TWO) error("Invalid 16bit register");
			else
			if(size == REG_INT && cnt > REG_ONE) error("Invalid 32bit register");
	
			tok->str[3] = (usigned | size | cnt);

		} else {
			if(curch == 'S') {
				tok->str[0] = curch;
				ffwd();
				if(islower(curch)) curch = toupper(curch);
				if(curch == 'P') {
					tok->str[1] = curch;
					tok->str[2] = 0;
					ctok = TOK_SP;
					ffwd();
				} else {
					error("stray '%%' in code");
				}
			} else if(curch == 'R') {
				tok->str[0] = curch;
				ctok = TOK_R;
				ffwd();
			} else
				error("stray '%%' in code");

			return;
		}

		if(ctok == TOK_R) {
			return;	
		}

		tok->ch = (tmp[0] - 'A') * 6;
		tok->ch += tmp[1] - 'A';

		ctok = TOK_REGISTER;
		
   } else
   if(issymbol(curch)) {
   // get a symbol: +*/%()[]{} etc...

      if(!_resize_tok_str(2)) error("out of memory");
      tok->str[0] = curch; tok->str[1] = 0;

	   ctok = issym();

	   ffwd();
   } else
   if(isnum(curch)) {
   // get a number: base 10 [un]signed, or hex
      typ = getnum();
      ctok = typ;
      
   } else if(is_alpha(curch)) {
   // get a keyword or identifier.
      spos = fpos;
      while(isalphanum(curch)) {
         ffwd();
         if(finished) error("EOF after '%s'",tok->str);
      }
      epos = fpos;

      if(!_resize_tok_str(epos-spos+1)) error("out of memory");
      memcpy(tok->str,cur_input->file->data+spos,epos-spos);
      skipwhite();

      typ = iskeyword();
      if(typ!=TOK_UNKNOWN) ctok = typ;
      else ctok = TOK_IDENT;

   } else { ctok=TOK_UNKNOWN; ffwd(); }
   skipwhite();

}

int required(char *name)
{
   int i=0;
   for(i=0; filelist[i].name; i++) {
      if(strcmp(name,filelist[i].name)==0) return 1;
   }

   return 0;
}

int open_input(char *dat, int len, char *file)
{
   file_count++;

   if(file_count>=MAX_DEPTH) return 0;

   dat[len] = '\n';
   dat[len+1] = EOF;

   filelist[file_count].name = calloc(1,strlen(file)+1);
   if(!filelist[file_count].name) return 0;
   strcpy(filelist[file_count].name,file);

   filelist[file_count].file = mem_fopen(len+1, dat);
   if(!filelist[file_count].file) return 0;

   cur_input = &filelist[file_count];
   cur_input->linenum = 1;
   ffwd();

   return 1;
}

int open_file_for_input(char *file)
{
   FILE *f = NULL;
   int len = 0;
   char *dat = NULL;

   if(required(file)) return 0;

   f = fopen(file,"rb");
   if(!f) return 0;

   fseek(f, 0, SEEK_END);
   len = ftell(f);
   fseek(f, 0, SEEK_SET);


   dat = calloc(len+2, sizeof(char));
   if(!dat) return 0;

   fread(dat, sizeof(char), len, f);
   if(ferror(f)) return 0;

   if(!open_input(dat,len,file)) { log("open_input failed"); return 0; }

   return 1;
}

char *code = NULL;
int code_pos = 0;
int code_len = 0;
int got_func = 0;
_cxe_hdr *cxe = NULL;

int get_lable(char *name)
{
	symbol_t *sym = NULL;
	if(!name) return -1;

	sym = lookup_sym(lables, name);
	if(!sym) {
		return -1;
	}

	return sym->pos;
}

void set_lable(char *name, int pos)
{
	symbol_t *sym = NULL;
	if(!name) return;

	sym = lookup_sym(lables, name);
	if(sym) {
		sym->pos = pos;
	}
	else {
		error("Error: lable doesn't exist");
	}
}

void add_lable(char *name)
{
	symbol_t *sym = NULL;
	if(get_lable(name)>-1) error("%s label allready defined", name);

	sym = insert_sym(lables, name);
	if(!sym) error("out of memory");
	sym->pos = code_pos;

	if(got_func) {
		cxe_add_symbol(cxe, name, code_pos);
		got_func = 0;
	}
}



void add_patch(char *name, int arg)
{
	symbol_t *sym = NULL;
	if(get_lable(name)>-1) error("%s lable is defined? so why has add_patch() been called?", name);

	sym = insert_sym(patch, name);
	if(!sym) error("out of memory");

	// if it wasn't for the way i do this the compiled machine code could get smaller...
	if(arg>1)
		sym->pos = code_pos+11;
	else
		sym->pos = code_pos+4;
		
	//warn("lable %s doesn't exist so i'm going to have to patch it in later. [%i]", name, code_pos);
}

// apply all the patches
void back_patch(void)
{
	symbol_t *sym = NULL;
	symbol_t *tmp = NULL;
	int i,j=0;

	for(i=0; i<PRIME; i++) {
		tmp = patch[i];
		
		while(tmp) {
			sym = lookup_sym(lables, tmp->name);
			if(!sym) {
				fprintf(stderr, "symbol '%s' not found!\n", tmp->name);
				exit(0);
			}

			// add in what might already exist (for args that used expressions [...]) as they arn't actually mem args.
			// mem args will always be '0' before here.
			j = ((*(code+tmp->pos)) | (*(code+tmp->pos+1)<<8) | (*(code+tmp->pos+2)<<16) | (*(code+tmp->pos+4)<<24));
			if(!j) j += sym->pos;
			else j = sym->pos;

			*(code+tmp->pos) = (char)j;
			*(code+tmp->pos+1) = j >> 8;
			*(code+tmp->pos+2) = j >> 16;
			*(code+tmp->pos+3) = j >> 24; 

			tmp = tmp->next;
		}
	}
}

void _resize_code_buffer(int len)
{
	code = realloc(code, len);
	if(!code) error("out of memory");
	code_len = len;
}

void emit_code(unsigned char op, char typ1, int ref1, char reg1, int arg1, char typ2, int ref2, char reg2, int arg2)
{
	if(!code_len || code_pos+16 >= code_len) {
		_resize_code_buffer(code_len+1024);
	}

#ifdef DEBUG
	log("[%3i:%3i]: %3i ", code_pos, ctok_line, op);
#endif

	*(code+code_pos++) = op;

	if(opargs[(int)op].args) {
		*(code+code_pos++) = (char)ref1;
		*(code+code_pos++) = reg1;
		*(code+code_pos++) = typ1;
		*(code+code_pos++) = (char)arg1;

#ifdef DEBUG
		log("%3i %3i %3i %3i", ref1, reg1, typ1, arg1);
#endif

		//if(op > AND || ref1) {
			*(code+code_pos++) = arg1 >> 8;
			*(code+code_pos++) = arg1 >> 16;
			*(code+code_pos++) = arg1 >> 24;
		//}
	}

	if(opargs[(int)op].args>1) {
		*(code+code_pos++) = (char)ref2;
		*(code+code_pos++) = reg2;
		*(code+code_pos++) = typ2;
		*(code+code_pos++) = (char)arg2;

#ifdef DEBUG
		log("%3i %3i %3i %3i", ref2, reg2, typ2, arg2);
#endif

		//if(op > AND || ref2) {
			*(code+code_pos++) = arg2 >> 8;
			*(code+code_pos++) = arg2 >> 16;
			*(code+code_pos++) = arg2 >> 24;
		//}
	}
#ifdef DEBUG
	log("\n");
#endif
}

void _do_ident(void);

void _do_arg(char *typ, char *reg, int *arg, int *ref, int n)
{
	int gpar = 0;
	*ref=0;

	if(ctok==TOK_NUM || ctok==TOK_HEXNUM) {
		*typ = argLIT;
		*arg = tok->num;
		gettok();
	} else
	if(ctok==TOK_MEM) {
		*typ = argMEM;
		gettok();
		
		if(ctok==TOK_LPAREN) {
			gettok();
			gpar = 1;
		}
		
		if(ctok!=TOK_NUM && ctok!=TOK_HEXNUM) {
			if(ctok==TOK_REF) {
				*typ = argMEM;
				*ref = 1;
				gettok();

				if(ctok!=TOK_NUM && ctok!=TOK_HEXNUM)
					error("only ints can be mem REF's");

				*arg = tok->num;
			} else {
				*typ = argREG;

				if(ctok==TOK_REGISTER) {
					*ref = 1;
					*arg = tok->ch;
					*reg = tok->str[3];
					gettok();
				}
				else if(ctok==TOK_SP) {
					*ref = 1;
					*arg = REGSP;
					gettok();
				}
				else if(ctok==TOK_R) {
					*ref = 1;
					*arg = REGRET;
					*reg = 0;
					gettok();
				}
				else if(ctok==TOK_IDENT || (ctok >= 0  && ctok <= MAX_OPS)) {
					*typ = argMEM;
					*arg = get_lable(tok->str);
					if(*arg==-1) add_patch(tok->str, n);
					gettok();
				}
				else if(ctok==TOK_LBRACKET) {
					*typ = argMEM;
					*arg = _do_expression(n);
					gettok();
				}
				else
					error("expected an ident or integer or a register [%i]",ctok);
			}
		} else {
			*arg = tok->num;
			*ref = 0;
			gettok();
		}

		if(gpar && ctok==TOK_RPAREN)       gettok();
		else if(!gpar && ctok==TOK_RPAREN) error("unmached right paren");
		else if(gpar && ctok!=TOK_RPAREN)  error("unmached left paren");
	} else
	if(ctok==TOK_ST) {
		*typ = argST;
		gettok();
		if(ctok==TOK_LPAREN) { gettok(); gpar = 1; }
		
		if(ctok == TOK_REGISTER) {
			*arg = tok->num;
			*ref = 2;
		} else
		if(ctok == TOK_SP) {
			*arg = REGSP;
			*ref = 2;
		} else
		if(ctok == TOK_R) {
			*arg = REGRET;
			*ref = 2;
		} else
		if(ctok!=TOK_NUM && ctok!=TOK_HEXNUM) error("expected an integer");
		else {
			*arg = tok->num;
		}
		
		gettok();

		if(gpar && ctok==TOK_RPAREN) gettok();
		else if(!gpar && ctok==TOK_RPAREN) error("unmached right paren");
		else if(gpar && ctok!=TOK_RPAREN) error("unmached left paren");
	} else
	if(ctok==TOK_REGISTER) {
		*typ = argREG;
		*arg = tok->ch;
		*reg = tok->str[3];
		gettok();
	} else
	if(ctok==TOK_SP) {
		*typ = argREG;
		*arg = REGSP;
		gettok();
		
	} else
	if(ctok==TOK_R) {
		*typ = argREG;
		*arg = REGRET;
		gettok();
	} else
	if(ctok==TOK_ARG) {
		*typ = argST;
		*ref = 1;

		gettok();
		if(ctok!=TOK_NUM) error("unknown stack ($) arg '%s'", tok->str);
		*arg = tok->num+1;

		gettok();
		
	} else
	if(ctok==TOK_LBRACKET) {
		*typ = argMEM;
		*arg = _do_expression(n);
		warn("got %i from _do_expression()", *arg);
		gettok();
	} else
	if(ctok==TOK_IDENT || (ctok >= 0 && ctok <= MAX_TOK)) {
		*typ = argLIT;
		*arg = get_lable(tok->str);
		if(*arg==-1) add_patch(tok->str, n);
		gettok();
	}
	else
		error("unknown arg after op '%s'", tok->str);
}

void _do_op(unsigned char op)
{
	unsigned char typ1=0, typ2=0, reg1=0, reg2=0;
	int arg1=0, arg2=0, ref1=0, ref2=0;

	if(opargs[(int)op].op != op)
#ifdef DEBUG
		error("Casm seems to think that op %i is '%s' but CHIP says '%s'", op, opstr[op], opstr[opargs[(int)op].op]);
#else
		error("CHIP and Casm are not in sync.");
#endif

	if(curch == ':') {
		_do_ident();
		return;
	}

	gettok();
	if(opargs[(int)op].args && ctok!=TOK_NL && ctok!=TOK_SEMI && !eq(EOF) && ctok != TOK_UNKNOWN) {

		_do_arg(&typ1, &reg1, &arg1, &ref1, 1);

		if(opargs[(int)op].args > 1 && ctok!=TOK_NL && ctok!=TOK_SEMI && !eq(EOF) && ctok != TOK_UNKNOWN) {
			gettok();
			
			_do_arg(&typ2, &reg2, &arg2, &ref2, 2);
		} else
		if(!opargs[(int)op].args || opargs[(int)op].args > 1)
#ifdef DEBUG
			error("incorrect amount of args(1). op '%s' takes %i", opstr[op], opargs[(int)op].args);
#else
			error("incorrect amount of args(1). op '%i' takes %i", op, opargs[(int)op].args);
#endif
	} else
	if(opargs[(int)op].args)
#ifdef DEBUG
			error("incorrect amount of args(0). op '%s' takes %i", opstr[op], opargs[(int)op].args);
#else
			error("incorrect amount of args(0). op '%i' takes %i ['%i']", op, opargs[(int)op].args, ctok);
#endif

	if(ctok!=TOK_NL && ctok!=TOK_SEMI && !eq(EOF)) error("missing [return] after '%s' [%i] op==%i",tok->str,ctok,op);
	gettok();
	
	emit_code(op, typ1, ref1, reg1, arg1, typ2, ref2, reg2, arg2);
}

void _do_ident(void)
{
	char *str = NULL;

	str = calloc(strlen(tok->str)+1, sizeof(char));
	if(!str) error("out of memory");

	strcpy(str, tok->str);

	gettok();
	if(ctok!=TOK_COLON) error("missing colon after lable '%s'",str);
	gettok();

	add_lable(str);
	free(str);
}

void comp(void);
void _do_include(void)
{
	char *str = NULL;
	int fnum = 0;
	
	gettok();
	if(ctok!=TOK_STR) error("missing \"file\" after 'include'");

	str = calloc(strlen(tok->str)+1, sizeof(char));
	if(!str) error("out of memory");

	strcpy(str, tok->str);

	if(!open_file_for_input(str)) error("%s could not be included",str);
	gettok();

	fnum = file_count;
	while(fnum==file_count) comp();
}

void _do_db(void)
{
	int dlen = 0, i=0;
	int ppos = 0;

	gettok();
	if(ctok==TOK_STR) {
		dlen = strlen(tok->str)+1;

		if(!code_len || code_pos+dlen >= code_len) {
			_resize_code_buffer(code_len+dlen+1024);
		}

		ppos = code_pos;

		for(i=0; i<dlen; i++) {
			*(code+code_pos) = tok->str[i];
			code_pos++;
		}

		*(code+code_pos) = 0;

		// pad the end of the string so it lies on a long word boundary (32bit)
		ppos = code_pos - ppos;
		ppos %= 5;

		for(i=0; i<ppos+4; i++) {
			emit_code(NOOP, 0, 0, 0, 0, 0, 0, 0, 0);
		}
		
		gettok();
	} else error("'DB' only supports strings... :(");

	if(ctok!=TOK_NL && ctok!=TOK_SEMI && !eq(EOF)) error("missing [return] after DB decl");
	else gettok();
}

void _do_dw(void)
{
	gettok();
	if(ctok == TOK_NUM || ctok == TOK_HEXNUM) {
		if(!code_len || code_pos+sizeof(int) >= code_len) {
			_resize_code_buffer(code_len+sizeof(int)+1024);
		}

		*(code+code_pos++) = (char)tok->num;
		*(code+code_pos++) = tok->num >> 8;
		*(code+code_pos++) = tok->num >> 16;
		*(code+code_pos++) = tok->num >> 24;

		gettok();
	} else
		error("an integer or hexadecimal number must follow 'DW'");
}


void _do_func(void)
{
	if(!cxe) return;

	gettok();

	got_func = 1;
}

void _do_entry(void)
{
	if(!cxe) return;

	gettok();

	cxe->entry = code_pos;
}

void _do_import(void)
{
	int imp = -1;
	char *tmp = NULL;

	if(!cxe) return;

	gettok();

	if(ctok != TOK_STR) error("parse error after 'IMPORT' statement. expected a string");

	imp = cxe_add_imp_file(cxe, tok->str);
	if(imp < 0) error("Couldn't create a new import decl.");

	gettok();

	if(ctok == TOK_NL) gettok();

	while(ctok == TOK_COMMA) {
		gettok();
		if(ctok == TOK_NL) gettok();
		if(ctok != TOK_STR) error("expected a string");

		/* change the symbol to its actual name */
		tmp = calloc(1, strlen(tok->str)+5);
		if(!tmp) error("Out of memory");

		sprintf(tmp, "IMP_%s", tok->str);
		
		cxe_add_imp_symbol(cxe, imp, tmp);

		/* create virtual lables so import funcs are easier to use */
		add_lable(tmp);
		set_lable(tmp, cxe->total_imp_cnt-1);
		free(tmp);

		gettok();
	}

	if(ctok != TOK_SEMI) error("parse error after 'IMPORT' statement. expected a semi colon");
	gettok();

}

void comp(void)
{
	switch(ctok) {
		case TOK_IDENT:
			_do_ident();
			break;

		case TOK_DB:
			_do_db();
			break;

		case TOK_DW:
			_do_dw();
			break;

		case TOK_NL:
		case TOK_SEMI:
			gettok();
			break;

		case TOK_INCLUDE:
			_do_include();
			break;

		case TOK_ENTRY:
			_do_entry();
			break;

		case TOK_FUNC:
			keywords_valid = 0;
			_do_func();
			keywords_valid = 1;
			break;

		case TOK_IMPORT:
			_do_import();
			break;

		default:
			_do_op(ctok);
	}
}

#define DO_TYPE(type) ({\
   if(type==TOK_NUM) printf("token == %i %i\n",tok->num, ctok); \
   else if(type!=TOK_UNKNOWN) printf("token == %s %i\n",tok->str, ctok); \
   else error("unknown token found [%s/%i]",tok->str,ctok);\
})

int parse_mini(char *file)
{
   if(!open_file_for_input(file)) return 0;

	cxe = calloc(1, sizeof(_cxe_hdr));
	if(!cxe) return 0;

   gettok(); // get first token
   while(!finished) {
      comp();
   }

	back_patch();

   return 1;
}

#include <stdlib.h>

/*
-i => infile
-o => outfile
-h => info
-v => version

the -i opt isnt needed if the first opt
is a casm assembly file and the -o opt
isnt needed if you want to save the cxe file with the same
base name as the input file. If the -e opt is spesified then
the -o opt (if exists) is ignored.

 */

void print_help(char *exe, char *message)
{
	if(message) printf("%s\n", message);
	printf("Usage: %s [OPTIONS]\n"
			 "   or: %s [-i] <input>.chi -o <output>.cxe -- compiles <input>.chi to <output>.cxe\n"
			 "   or: %s [-i] <input>.chi -- compiles <input>.chi to <input>.cxe\n"
			 "\n"
			 "  -i, --input   define input file\n"
			 "  -o, --output  define output file\n"
			 "  -h, --help    print this help\n"
			 "  -v, --version print version info and exit\n", exe, exe, exe);
}

int has_suffix(char *file, char *suf)
{
	int len=strlen(file)-1;
	int slen=strlen(suf)-1;

	while(len > -1 && slen > -1) {
		if(file[len--] != suf[slen--]) return 0;
	}

	return 1;
}

char *get_basename(char *file)
{
	char tmp[255], *t = NULL;
	int len = strlen(file)-1,post=1,dot=0;

	memset(tmp, 0, 255);

	while(len > -1) {
		if(file[len] != '.' && post) {
			--len;
			continue;
		} else {
			if(post) {
				len--;
				dot = len;
			}
			post = 0;
		}
		tmp[len] = file[len];
		--len;

		if(file[len] == '/' || file[len] == '\\') {
			int i=dot;

			while(i-len>-1) { tmp[i-len] = tmp[i-len+dot-1]; i--; }
			break;
		}
	}

	t = calloc(1, strlen(tmp)+5);
	if(!t) return NULL;

	strcpy(t, tmp);
	return t;
}

int main(int argc, char **argv)
{
	int i=0;
	char *input=NULL, *output=NULL;

	if(argc < 2) {
		print_help(argv[0], "please specify an input file");
		return 0;
	}
	
	if(*argv[1] != '-') {
		input = calloc(strlen(argv[1])+1, sizeof(char));
		if(!input) return 0;
		strcpy(input, argv[1]);

	}/*else if(!argv[1][1]) {
		input = calloc(2, sizeof(char));
		if(!input) return 0;
		strcpy(input, "-");
	}*/ // FIXME: create a file subsystem so stdin is a valid input file :)

	for(i=1; i<argc; i++) {
		if(*argv[i] == '-') strcpy(argv[i], argv[i]+1);
		if(strcmp(argv[i],"i")==0 || strcmp(argv[i],"-input")==0) {
			if(input) {
				print_help(argv[0], "please only specify one input file");
				return 0;
			}

			if (++i >= argc) break;
			
			input = calloc(strlen(argv[i])+1,sizeof(char));
			strcpy(input, argv[i]);

      } else if(strcmp(argv[i],"o")==0 || strcmp(argv[i],"-output")==0) {
         if (++i >= argc) break;
         output = calloc(strlen(argv[i])+1, sizeof(char));
         if(!output) return 0;
         
         strcpy(output, argv[i]);

      } else if(strcmp(argv[i],"h")==0 || strcmp(argv[i],"-help")==0 || strcmp(argv[i],"?")==0) {
         print_help(argv[0], NULL);
         return 0;

      } else if(strcmp(argv[i],"v")==0 || strcmp(argv[i],"-version")==0) {
			printf("%s v0.01\n", argv[0]);
			return 0;

      }
   }

	if(has_suffix(input, ".asm") || has_suffix(input, ".chi")) {
		if(!parse_mini(input)) {
			fprintf(stderr,"couldn't open %s\n",input);
			exit(errno);
		}
		
		cxe->data = code;
		cxe->data_len = code_pos;

	} else {
		while(*input && *input != '.') input++;
		printf("Just what exactly am I suposed to do with a '%s' file?\n", input);
		print_help(argv[0], NULL);
		return 0;
	}
	
	if(!output) {
		output = get_basename(input);
		if(!output) printf("couldn't allocate output file name\n");
		strcat(output, ".cxe");
	}

	if(!save_cxe(cxe, output)) {
		printf("couldn't save '%s'...\n", output);
		return 0;
	}

	printf("compiled %s and saved as %s\n", input, output);

	free_cxe_hdr(cxe);
	free(input);
	free(output);

   return 0;
}


struct casm_instance_t *casm_create_instance(void)
{
	struct casm_instance_t *instance = NULL;
		
	instance = calloc(1, sizeof(struct casm_instance_t));
	if(!instance) return NULL;
		
	return instance;
}

void casm_destroy_instance(struct casm_instance_t *instance)
{
	if(!instance) error("instance == NULL");
	
	free(instance);
}

int casm_add_file(struct casm_instance_t *instance, char *file)
{
	if(!instance) error("instance == NULL");
	if(!file) error("file == NULL");	
	
	instance->input_count++;
	instance->input_list = realloc(instance->input_list, sizeof(struct inputfile_t *) * instance->input_count);
	if(!instance->input_list) error("out of memory");
	
	instance->input_list[instance->input_count-1] = calloc(1, sizeof(struct inputfile_t));
	if(!instance->input_list[instance->input_count-1]) error("out of memory");

	instance->input_list[instance->input_count-1]->name = calloc(1, strlen(file)+1);
	if(!instance->input_list[instance->input_count-1]->name) error("out of memory");
}

int casm_process(struct casm_instance_t *instance);

int  casm_add_mode(struct casm_instance_t *instance, struct charsym_t *mode);
void casm_set_master_mode(struct casm_instance_t *instance, int mode);
void casm_set_mode(struct casm_instance_t *instance, int mode);

symbol_t *casm_add_symbol(struct casm_instance_t *instance, char *name);
symbol_t *casm_add_patch(struct casm_instance_t *instance, char *name);



