/*
Program:	Assembler.cpp
Author:		Michael J. Sepcot - ASProcessor
Date:		November 19, 2003
Purpose:	Create a two pass MIPS limited instruction set assembler
			for use in MAX PLUS II 10.2 BASELINE by turning low level
			code into machine code.  
(c) 2003	ASProcessor - Michael J. Sepcot, Chris Vafinis, Nate Johnston, Antonis Antoniou
*/

#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
using namespace std;

struct symbol
{
	string label;					// Holds Label Name
	int address;					// Holds Label Address
};

/* Function Declaration */

// First Pass
void Parse(string filename, vector<symbol> &table);
bool check(string word);

// Second Pass
void Convert(string filename, vector<symbol> table);
string reg(string word);
int find(string word, vector <symbol> table);
void Binary(int number, int count, ofstream &outfile);
void shamt(int number, int count, ofstream &outfile);

//*****************************************************************************************

void main()
{
	string filename;				// Name of File to Read
	vector <symbol> table;			// Symbol Table

	cout << "**********************************" << endl;
	cout << "**     MIPS to Machine Code     **" << endl;
	cout << "** Coded By:  Michael J. Sepcot **" << endl;
	cout << "**          ASProcessor         **" << endl;
	cout << "**********************************" << endl;
	cout << endl;
	cout << "Enter Name of File to Convert (ie. filename.s): ";
	cin >> filename;
	Parse(filename, table);			// Parse Document and Set Symbol Table
	Convert(filename, table);		// Reparse Document and Convert MIPS to Machine Code
	cout << endl;
}

//*****************************************************************************************
// First Pass of Document *** Set Symbol Table
void Parse(string filename, vector<symbol> &table)
{
	ifstream infile;				// Input File
	string line;					// Command Line
	int count = 0;					// Instruction Number
	infile.open(filename.c_str());	// Open File

	// Error Check - Ensures a File is Opened
	if(!infile.is_open())			// Quit Assembler If Bad File Name
	{
		cout << endl;
		cout << "**********************************" << endl;
		cout << "**       * READ ERROR *         **" << endl;
		cout << "**                              **" << endl;
		cout << "**  The Specified File Was Not  **" << endl;
		cout << "**  Located In This Directory.  **" << endl;
		cout << "**  Please Reload The Program   **" << endl;
		cout << "**  And Try Again.              **" << endl;
		cout << "**                              **" << endl;
		cout << "**********************************" << endl;
		cout << endl;
		return;
	}

	getline(infile, line);			// Prime Loop


	// All Information Above the .text Instruction is Taken as a Comment
	while(line != ".text" && !infile.eof())
		getline(infile, line);

	while (!infile.eof())			// Until No More Commands Available
	{
		line = line.substr(0, line.find(" "));	// Get Instruction

		// If Not Known, Instruction is a Label
		if (!check(line))
		{
			line = line.substr(0, line.find(":"));		// Get Label
			table.resize(table.size() + 1);
			table[table.size() - 1].label = line;		// Store Label Name
			table[table.size() - 1].address = count;	//		And Address
		}
		else
			count++;				// Increase Command Location
		getline(infile, line);		// Get Next Line
	}
	infile.close();					// Close File
}

// Check to See if Command is Known *** If Not, Command is a Label
bool check(string word)
{
	if (	word == "and"	|| word == "or"		|| word == "add"	|| word == "jr"		||
			word == "sll"	|| word == "srl"	|| word == "sub"	|| word == "slt"	||
			word == "beq"	|| word == "bne"	|| word == "lw"		|| word == "sw"		||
			word == "andi"	|| word == "ori"	|| word == "addi"	|| word == "subi"	||
			word == "slti"	|| word == "j"		|| word == "jal"	|| word == ".text")
		return true;					// If Know Command Then Return 1
	else
		return false;					// Else Command is a Label
}

//*****************************************************************************************
// Second Pass of Document *** Convert MIPS to Machine Code
void Convert(string filename, vector<symbol> table)
{
	string	word,					// Command
			name,					// Output File Name
			opCode,					// Operation Code
			rs,						// RS
			rt,						// RT
			rd,						// Destination Register
			fCode;					// Function Code
	int number;						// Number
	int instr = 0;					// Count The Instruction Number
	ifstream infile;				// Input File
	ofstream outfile;				// Output File

	infile.open(filename.c_str());	// Open Input File
	
	// Error Check - Ensures a File is Opened
	if(!infile.is_open())
		return;						// Quit Assembler If Bad File Name
	
	cout << "Enter Name of File to Save Machine Code To (ie filename.mif): ";
	cin >> name;					// Get File Name
	
	cout << endl;
	cout << "*** ERROR LIST ****" << endl;
	cout << endl;

	outfile.open(name.c_str());		// Open Output File

	outfile << "---------------------------------" << endl;
	outfile << "--  .mif File For ASProcessor  --" << endl;
	outfile << "--    Created With Assembler   --" << endl;
	outfile << "-- Coded By: Michael J. Sepcot --" << endl;
	outfile << "---------------------------------" << endl;

	infile >> word;					// Get Command

	// All Information Above the .text Instruction is Taken as a Comment
	while(word != ".text" && !infile.eof())
	{
		if (word == "#")
		{
			string line;
			getline (infile, line);
			outfile << "--" << line << endl;
		}
		infile >> word;
	}

	if (infile.eof())
		cout << "Missing \".text\" -- No Code Was Read In" << endl;

	// Begining File Definition
	outfile << endl;
	outfile << "WIDTH = 16;" << endl;
	outfile << "DEPTH = 256;" << endl;
	outfile << endl;
	outfile << "ADDRESS_RADIX = DEC;" << endl;
	outfile << "DATA_RADIX = BIN;" << endl;
	outfile << endl;
	outfile << "CONTENT BEGIN" << endl;
	outfile << "\t" << instr << "\t:\t" << "0000;";

	while (!infile.eof())
	{
		if (check(word))
		{
			// Check R-Type Instructions
			if (	word == "and"	|| word == "or"		||
					word == "add"	|| word == "sub"	|| word == "slt"	)
			{
				instr++;
				opCode = "0000";		// R-Type Operation Code
	
				if (word == "and")
					fCode = "000";		// And Function Code
				else if (word == "or")
					fCode = "001";		// Or Function Code
				else if (word == "add")
					fCode = "010";		// Addition Function Code
				else if (word == "sub")
					fCode = "110";		// Subtraction Function Code
				else if (word == "slt")
					fCode = "111";		// Split If Less Than Function Code

				infile >> word;
				word = word.substr(0,2);
				rd = reg(word);			// Set Destination Register
		
				infile >> word;
				word = word.substr(0,2);
				rs = reg(word);			// Set Source Register
		
				infile >> word;
				word = word.substr(0,2);
				rt = reg(word);			// Set Source/Destination Register

				// Set Instruction In File
				outfile << endl;
				outfile << "\t" << instr << "\t:\t";
				outfile << "0000" << rs << rt << rd << "000" << fCode << ";";
			}
		
			else if (	word == "sll"	|| word == "srl"	)
			{
				instr++;
				opCode = "0000";		// R-Type Operation Code

				if (word == "sll")
					fCode = "100";		// Shift Left Logical Function Code
				else if (word == "srl")
					fCode = "101";		// Shift Right Logical Function Code

				rs = "00";				// Set Source Register to Zero (not important)

				infile >> word;
				word = word.substr(0,2);
				rd = reg(word);			// Set Destination Register
		
				infile >> word;
				word = word.substr(0,2);
				rt = reg(word);			// Set Source/Destination Register

				infile >> number;		// Get Shift Amount			

				// Set Instruction In File
				outfile << endl;
				outfile << "\t" << instr << "\t:\t";
				outfile << opCode << rs << rt << rd;
				shamt(number, 0, outfile);
				outfile << fCode << ";";
			}

			// Check I-Type Instructions (Simple Cases Only)
			else if (	word == "andi"	|| word == "ori"	||
						word == "addi"	|| word == "subi"	|| word == "slti")
			{
				instr++;

				if (word == "andi")
					opCode = "1000";	// And Immediate Function Code
				else if (word == "ori")
					opCode = "1001";	// Or Immediate Function Code
				else if (word == "addi")
					opCode = "1010";	// Addition Immediate Function Code
				else if (word == "subi")
					opCode = "1110";	// Subtraction Immediate Function Code
				else if (word == "slti")
					opCode = "1111";	// Split If Less Than Immediate Function Code

				infile >> word;
				word = word.substr(0,2);
				rt = reg(word);			// Set Destination Register

				infile >> word;
				word = word.substr(0,2);
				rs = reg(word);			// Set Source Register
			
				// Set Instruction In File
				outfile << endl;
				outfile << "\t" << instr << "\t:\t";
				outfile << opCode << rs << rt;
				infile >> number;
				Binary(number, 0, outfile);
				outfile << ";";
			}

			// Check Branch Instructions
			else if (	word == "beq"	|| word == "bne"	)
			{
				instr++;

				if (word == "beq")
					opCode = "1011";	// Branch On Equal Function Code
				else if (word == "bne")
					opCode = "1101";	// Branch On Not Equal Function Code

				infile >> word;
				word = word.substr(0,2);
				rs = reg(word);			// Set Source Register

				infile >> word;
				word = word.substr(0,2);
				rt = reg(word);			// Set Destination Register
			
				infile >> word;
				number = find(word, table);

				// Set Instruction In File
				outfile << endl;
				outfile << "\t" << instr << "\t:\t";
				outfile << opCode << rs << rt;
				Binary(number, 0, outfile);
				outfile << ";";
			}

			// Check Load/Store Word Instructions
			else if (	word == "lw"	|| word == "sw"		)
			{
				instr++;

				if (word == "lw")
					opCode = "0100";	// Load Word Function Code
				else if (word == "sw")
					opCode = "0110";	// Store Word Function Code

				infile >> word;
				word = word.substr(0,2);
				rt = reg(word);			// Set Destination Register

				infile >> number;

				infile >> word;
				word = word.substr(1,2);
				rs = reg(word);			// Set Source Register

				// Set Instruction In File
				outfile << endl;
				outfile << "\t" << instr << "\t:\t";
				outfile << opCode << rs << rt;
				Binary(number, 0, outfile);
				outfile << ";";
			}

			// Check Jump Instructions
			else if (word == "j" || word == "jal")
			{
				instr++;

				if (word == "j")
					opCode = "0010";	// Jump Function Code
				else if (word == "jal")
					opCode = "0011";	// Jump And Link Function Code

				infile >> word;
				number = find(word, table);

				// Set Instruction In File
				outfile << endl;
				outfile << "\t" << instr << "\t:\t";
				outfile << opCode << "0000";
				Binary(number, 0, outfile);
				outfile << ";";
			}

			else if (word ==  "jr")
			{
				instr++;

				fCode = "011";			// Jump Return Function Code
				opCode = "0000";		// R-type Operatino Code
				infile >> word;
				rd = reg(word);			// Set Destination Register

				// Set Instruction In File
				outfile << endl;
				outfile << "\t" << instr << "\t:\t";
				outfile << opCode << rd << "0000000" << fCode << ";";
			}
		}
		else if (word == "#")			// Check For Comments
		{
			getline(infile, word);
			outfile << "\t--" << word;
		}
		else							// Check For Label
		{
			bool isLabel = false;
			for (int lcv = 0; lcv < table.size(); lcv++)
				if (table[lcv].label == word.substr(0, word.find(":")))
				{
					isLabel = true;
					outfile << endl;
				}
			if (!isLabel)				// Debug Tool - Unknown Instruction
			{
				cout << "Unable To Convert Line: \t" << word;
				getline(infile, word);
				cout << word << endl;
			}
		}
		
		infile >> word;				// Get Next Command
	}

	// Fill In Remaining Instructions With Null Value 0000; and End File
	outfile << endl;
	for (instr = instr + 1; instr < 256; instr++)
		outfile << "\t" << instr << "\t:\t" << "0000;" << endl;
	outfile << "END;" << endl;

	cout << endl;
	cout << "*** END OF ERROR LIST ***" << endl;

	outfile.close();				// Close Output File
	infile.close();					// Close Input File
}

// Convert Registers to Binary Values
string reg(string word)
{
	if (word == "$0")
		return "00";				// Register 0
	else if (word == "$1")
		return "01";				// Register 1
	else if (word == "$2")
		return "10";				// Register 2
	else if (word == "$3")
		return "11";				// Register 3
	else							// Debug Tool - Unknown Register
	{
		cout << "Unknown Register Name " << word << endl;
		return "XX";				// Register Does Not Exist
	}
}

// Search Symbol Table For Label Addresses
int find(string word, vector <symbol> table)
{
	int lcv = 0;					// Start At Begining
	while (lcv < table.size())		// Search For Label
	{
		if (word == table[lcv].label)
			return table[lcv].address;	// Return Label Address
		else
			lcv++;
	}
	//	Debug Tool - Unknown Label
	cout << "Address of Label " << word << " Not Found" <<endl;
	return -127;					// Return False Address If Label Not Found
}

// Convert Decimal Number To Binary Number With Sign Bit
void Binary(int number, int count, ofstream &outfile)
{
	// Set Sign Bit
	if (count == 0)
	{
		if (abs(number) > 127)		// Debug Tool - Overflow
		{
			cout << "Overflow Detected ";
			cout << number << " Exceeds Number Limit ( |number| cannot be > 127 )";
			cout << endl;
		}
		outfile << "1";				// 1 Incicates Negative Number
	}
	else if (number >= 0 && count == 0)
		outfile << "0";				// 0 Incicates Positive Number
	// Proceed to Find 7 Digit Binary Equivalant
	if (count < 7)
	{
		int quotient = number / 2;
		int remainder = abs(number) % 2;	// Compute Bit
		Binary(quotient, count + 1, outfile);
		outfile << remainder;		// Set Bit (High to Low)
	}
}

// Convert the Absolute Value of a Decimal Number To 3 Bit Binary Number
void shamt(int number, int count, ofstream &outfile)
{
	if (count == 0)					// Debug Tool - Bad Shift
	{
		if (number > 7)
			cout << "Overflow Detected " << number << " Exceeds Shift Limit of 7" << endl;
		if (number < 0)
			cout << "Cannot Shift Negative Values" << endl;
	}
	if (count < 3)
	{
		int quotient = number / 2;
		int remainder = abs(number) % 2;	// Compute Bit
		shamt(quotient, count + 1, outfile);
		outfile << remainder;		// Set Bit (High to Low)
	}
}