/*

INTERPRETER ANOTHER WAY...

AUHTOR: Matthew W. Coan
DATE: July 28, 2020 11:55 AM

*/

#define strcasecmp _stricmp

#include <assert.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <ctime>
#include <cstdlib>
#include <cstring>
#include "var.h"

namespace interp {

enum code_code { 
   PUSH_CODE, 
   ADD_CODE, 
   SUB_CODE, 
   MUL_CODE, 
   DIV_CODE,
   PRINT_CODE, 
   NOP_CODE, 
   HALT_CODE, 
   JMP_CODE, 
   CMP_CODE, 
   JZ_CODE, 
   APOP_CODE, 
   APUSH_CODE,
   CALL_CODE,
   RET_CODE,
};

class Interp;
class code;

typedef void (Interp::*code_type)(int &, int);

class code {
public:
   code_code opcode;
   var arg1;    
   string arg0;

   code() { 
      opcode = NOP_CODE; 
      arg1 = 0; 
   }
   code(const code & co) {
      opcode = co.opcode;
      arg1 = co.arg1;
      arg0 = co.arg0;
   }
   code(const code_code opcode, int arg1) {
      this->opcode = opcode;
      this->arg1 = arg1;
   }
   code(const code_code op,
        const var arg2) {
      this->opcode = op;
      arg1 = arg2;
   }
   code(const code_code op,
        const string & arg2) {
      this->opcode = op;
      arg1 = 0;
      this->arg0 = arg2;
   }
   code(const code_code opcode) {
      this->opcode = opcode;
      arg1 = 0;
   }
   ~code() { 
   }
   code & operator=(const code & cp) {
      opcode = cp.opcode;
      arg1 = cp.arg1;
      arg0 = cp.arg0;
      return *this;
   }
};

#define MAX_PROGRAM 16
#define MAX_MEMORY 16
#define MAX_STACK 16
#define MAX_INSTRUCTIONS 15

code_type CPU[MAX_INSTRUCTIONS];

class Interp {
protected:
   string input_file;
   var memory[MAX_MEMORY];
   code program[MAX_PROGRAM];
   var stk[MAX_STACK];
   int entry_point;
   int program_size;
   int memory_size;
   int max_stack;
   int stack_offset;
   int offset;
   bool halt;
   bool ret_flag;
   bool debug_flag;
   var retref;

   var & pop();
   void push(const var & val);

   void push_code(int & pc, int arg1);
   void print_code(int & pc, int arg1);
   void add_code(int & pc, int arg1);
   void sub_code(int & pc, int arg1);
   void mul_code(int & pc, int arg1);
   void div_code(int & pc, int arg1);
   void nop_code(int & pc, int arg1);
   void halt_code(int & pc, int arg1);
   void jmp_code(int & pc, int arg1);
   void cmp_code(int & pc, int arg1);
   void jz_code(int & pc, int arg1);
   void apop_code(int & pc, int arg1);
   void apush_code(int & pc, int arg1);
   void call_code(int & pc, int arg1);
   void ret_code(int & pc, int arg1);

public:
   Interp(const string & input_file);
   ~Interp();
   
   bool get_halt() { return halt; }
   void load();
   void run(int pc);
   void run();
};

class StrCompare {
public:
   bool operator()(const string & arg1, const string & arg2) const {
      return strcasecmp(arg1.c_str(),arg2.c_str()) < 0;
   }
};

void Interp::load() {
   program_size = 0;
   memory_size = MAX_MEMORY;
   max_stack = MAX_STACK;
   entry_point = 0;
   int id_count = 0;
   map< string, int, StrCompare > label_map;
   map< string, int, StrCompare > id_map;
   ifstream file(input_file, ios::in);
   if(!file.fail()) {
      cout << "file open..." << endl;
      string lbl,op,arg;
      int offset = 0;
      file >> lbl;
      cout << lbl << endl;
      while(!file.fail()) {
         arg = "";
         if(lbl.find(":") != string::npos) {
            lbl = lbl.substr(0,lbl.size()-1);
            file >> op;
            cout << op << endl;
         }
         else {
            op = lbl;
            lbl = "";
         }
         if(lbl.size()) {
            label_map[lbl] = offset;
         }
         if(strcasecmp(op.c_str(),"add") == 0) {
            cout << op << endl;
            program[offset] = code(ADD_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"dd") == 0) {
            cout << op << endl;
            if(arg.size() == 0) file >> arg;
            id_map[lbl] = id_count;
            memory[id_count] = atoi(arg.c_str());
            id_count++;
         }
         else if(strcasecmp(op.c_str(),"sub") == 0) {
            cout << op << endl;
            program[offset] = code(SUB_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"mul") == 0) {
            cout << op << endl;
            program[offset] = code(MUL_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"div") == 0) {
            cout << op << endl;
            program[offset] = code(DIV_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"apush") == 0) {
            cout << op << endl;
            if(arg.size() == 0) file >> arg;
            program[offset] = code(APUSH_CODE, arg);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"apop") == 0) {
            cout << op << endl;
            if(arg.size() == 0) file >> arg;
            program[offset] = code(APOP_CODE, arg);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"nop") == 0) {
            cout << op << endl;
            program[offset] = code(NOP_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"jmp") == 0) {
            cout << op << endl;
            if(arg.size() == 0) file >> arg;
            program[offset] = code(JMP_CODE, arg);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"jz") == 0) {
            cout << op << endl;
            if(arg.size() == 0) file >> arg;
            program[offset] = code(JZ_CODE, arg);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"print") == 0) {
            cout << op << endl;
            program[offset] = code(PRINT_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"halt") == 0) {
            cout << op << endl;
            program[offset] = code(HALT_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"cmp") == 0) {
            cout << op << endl;
            program[offset] = code(CMP_CODE);
            offset++;
         }
         else if(strcasecmp(op.c_str(),"push") == 0) {
            cout << op << endl;
            if(arg.size() == 0) file >> arg;
            code c(PUSH_CODE, atoi(arg.c_str()));
            program[offset] = c;
            offset++;
         }
         else {
            throw "bad instruction name...";
         }
         if(debug_flag) cout << lbl << "\t" << op << "\t" << arg << endl;
         file >> lbl;
         cout << lbl << endl;
      }
      file.close();
      program_size = offset;
      halt = false;
   }
   else {
      throw "unable to open input file...";
   }
   for(int i = 0; i < program_size; i++) {
      if(program[i].opcode == JZ_CODE || program[i].opcode == JMP_CODE) {
         program[i].arg1 = label_map[program[i].arg0];
      }
      else if(program[i].opcode == APUSH_CODE || program[i].opcode == APOP_CODE) {
         program[i].arg1 = id_map[program[i].arg0];
      }
   }
}


void interp_usage() {
   cout << "This software is the intellectual property of Matthew W. Coan..." << endl;
   cout << "Assembly Language Interpreter..." << endl << endl;
   cout << "Author: Matthew W. Coan" << endl;
   cout << "Email: matthewcoan1976@hotmail.com" << endl;
   cout << "Web: http://matthew1976.dns2go.com/~mcoan/" << endl;
   cout << "Compilation date: " << __DATE__ << " " << __TIME__ << endl << endl;
   cout << "usage: interp [ /help ] <program>.inp" << endl << endl;
   cout << "/help - this help message." << endl;
}

Interp::Interp(const string & file) {
   halt = false;
   offset = 0;
   stack_offset = 0;
   input_file = file;
   ret_flag = false;
   debug_flag = false;
   CPU[PUSH_CODE] = &Interp::push_code;
   CPU[ADD_CODE] = &Interp::add_code;
   CPU[SUB_CODE] = &Interp::sub_code;
   CPU[MUL_CODE] = &Interp::mul_code;
   CPU[DIV_CODE] = &Interp::div_code;
   CPU[PRINT_CODE] = &Interp::print_code;
   CPU[NOP_CODE] = &Interp::nop_code;
   CPU[HALT_CODE] = &Interp::halt_code;
   CPU[JMP_CODE] = &Interp::jmp_code;
   CPU[CMP_CODE] = &Interp::cmp_code;
   CPU[JZ_CODE] = &Interp::jz_code;
   CPU[APOP_CODE] = &Interp::apop_code;
   CPU[APUSH_CODE] = &Interp::apush_code;
   CPU[CALL_CODE] = &Interp::call_code;
   CPU[RET_CODE] = &Interp::ret_code;
}

Interp::~Interp() {
   
}

void Interp::run(int pc) {
   code* p_code = 0;
   bool temp = ret_flag;
   ret_flag = false;
   p_code = &program[pc];
   while(pc < program_size && !halt && !ret_flag) {
      p_code = &program[pc];
      (this->*(CPU[p_code->opcode]))(pc, p_code->arg1);
   }
   ret_flag = temp;
}

void Interp::call_code(int & pc, int arg1) {
   cout << "call_code" << endl;
   int temp_offset = stack_offset;
   stack_offset = offset;
   run(pc);
   stack_offset = temp_offset;
}

void Interp::ret_code(int & pc, int arg1) {
   ret_flag = true;
   pc++;
}

inline
var & Interp::pop() {
   if(offset <= 0) {
      throw "empty stack...";
   }
   offset--;
   retref = stk[offset];
   return retref;
}

inline
void Interp::push(const var & val) {
   if(offset >= max_stack) {
      throw "stack full...";
   }
   stk[offset] = val;
   offset++;
}

void Interp::div_code(int & pc, int arg1)
{
   var right = pop();
   var left = pop();
   var result = left / right;
   push(result);
   pc++;
}

void Interp::jmp_code(int & pc, int arg1)
{
   if(arg1 >= 14 || arg1 < 0) {
      throw "bad code address...";
   }
   pc = arg1;
}

void Interp::apush_code(int & pc, int arg1)
{
   if(arg1 >= memory_size || arg1 < 0) {
      throw "bad memory address...";
   }
   push(memory[stack_offset+arg1]);
   pc++;
}

void Interp::apop_code(int & pc, int arg1)
{
   if(arg1 >= memory_size || arg1 < 0) {
      throw "bad memory address...";
   }
   memory[stack_offset+arg1] = pop();
   pc++;
}

void Interp::cmp_code(int & pc, int arg1)
{
   var left,right;
   right = pop();
   left = pop();
   if(left == right) {
      push(0);
   }
   else if(left < right) {
      push(-1);
   }
   else {
      push(1);
   }
   pc++;
}

void Interp::jz_code(int & pc, int arg1)
{
   var ret;
   ret = pop();
   if(ret.to_int() == 0) {
      pc = arg1;
   }
   else {
      pc++;
   }
}

void Interp::halt_code(int & pc, int arg1)
{
   cout << "********** HALT ************" << endl;
   halt = true;
   pc++;
}

void Interp::print_code(int & pc, int arg1)
{
   var top;
   top = pop();
   cout << top.to_int() << endl;
   pc++;
}

void Interp::add_code(int & pc, int arg1)
{
   var left,right,result;
   right = pop(); 
   left = pop(); 
   result = left + right;
   push(result);
   pc++;
}

void Interp::sub_code(int & pc, int arg1)
{
   var right = pop(); 
   var left = pop(); 
   var result = left - right;
   push(result);
   pc++;
}

void Interp::mul_code(int & pc, int arg1)
{
   var right = pop(); 
   var left = pop(); 
   var result = left * right;
   push(result);
   pc++;
}

void Interp::push_code(int & pc, int arg1)
{
   var temp(arg1);
   push(temp);
   pc++;
}

void Interp::nop_code(int & pc, int arg1)
{
   pc++;
}

void Interp::run() {
   clock_t start_time;
   clock_t end_time;
   clock_t total;
   load();
   start_time = clock();
   call_code(entry_point, 0);
   end_time = clock();
   total = end_time - start_time;
   cout << "Total run time is " << total << " milliseconds..." << endl;
}

}


//
// MAIN ENTRY POINT
//

using namespace interp;

int
main(int argc, char ** argv)
{
   int rc = 0;
   bool help = false;
   string input_file;
   Interp * vm = 0;

   for(size_t i = 1; i < argc; i++) {
      if(strcasecmp(argv[i], "/help") == 0) {
         help = true;
      }
      else {
         input_file = argv[i];
      }
   }

   if(help || input_file.size() == 0) {
      interp_usage();
   }
   else {
      try {
         vm = new Interp(input_file);
         vm->run();
         delete vm;
         vm = 0;
      }
      catch(const char * message) {
         cerr << message << endl;
         rc = 1;
      }
      catch(...) {
         cerr << "interp error..." << endl;
         rc = 1;
      }
   }

   if(vm) {
      delete vm;
      vm = 0;
   }

   cout << "done..." << endl;

   return rc;
}
