/* N_105 assembler
 * Rosemary Francis 2004
 *
 *  Naive blow by blow translation of N_105 assembly into the *.dat file used by the 
 *  hardware implementation and software simulation
 *
 *  The source file must be a test file containing just the instructions with each token separated
 *  by a space (or new line). Registers are named in the form r<index no> and immediates are plain 
 *  text integers. Comments are started with a ; and terminated with a new line.
 *  
 *  Labels can be anything except a reserveed opcode. They can be placed anywhere between the end of 
 *  one instruction and the begining of the next. See the instruction set reference file for reserved 
 *  opcodes. In addition to the ones offically defined, this assembler also also supports "nop" (no op)
 *  which is equivalent to "or r0 r0". By convention every program should start with a label of the 
 *  same name as the file the program is in. This assembler is *not* case sensitive.
 * 
 *  Library functions may be included using #include <filename> notation. Only one file name per include 
 *  statement. The library funtion will be inserted at the point of the include statement. Labels must be 
 *  distinct.
 *  
 *  This assembler will allow any valid instruction. Invalid instrctions cause the Assembler to 
 *  terminate with an apprpriate error message and line number. Any other mistakes you may make (ie returning 
 *  before you have branched anywhere) are your own problem - good luck! Note you must end the file with a
 *  comment or instruction - *not* a label. 
 *  
 *  The destination is a .dat file usually called <instruction mem name>_lane0.dat found in the simulation 
 *  directory created by modelsim.
 *
 */

import java.io.*;
import java.lang.*;

public class Assembler_dat
{
    static String [] token_array;
    static int token_index = 0;
    static int token_array_length;
    static String token;
    static int line_num = 0;  //referes to line number in file program.txt
    static int instr_num = 0; //instruction num - used for branches
    static boolean pre_parse_in_progress = false; //indicates if the parse is in the pre parse or parse and write stage
    static boolean label_detected = false; //indicates if a label has been detected
    static boolean found_branch = false; //indicates if a branch has been detected

    public static void main(String [] args)
    {
	String assembly_file = "test.txt ";
	String destination_file = "onchip_rom.dat";
	
	try
	{
	    assembly_file = args[0];
	    destination_file = args[1];
	}
	catch(Exception e)
	    {
		System.out.println(e); 
		System.out.println("error : assembly file and destination data file expected"); 
		System.out.println("usage : java Assembler <your assembly>.txt <mem name>_lane0.dat");
		System.exit(1);
	    }

	read_and_lex(assembly_file);
	pre_parse_labels();
	parse_and_write(destination_file);
       
    }

    static void read_and_lex(String assembly_file)
    {

	String temp_assembly_file = "program.tmp";
       
	//deal with #includes by creating a tempory file

	try
	{
	    BufferedReader in = new BufferedReader(new FileReader(assembly_file));
	    BufferedWriter temp = new BufferedWriter(new FileWriter("program.tmp"));
	    char [] file_contents = new char [1<<16];
	    int character = ' ';
	    int n = 0;

	    create_linked_file(in, temp, assembly_file);	    
	    
	    in.close();
	    temp.close();

	    //read in the code

	    in = new BufferedReader(new FileReader(temp_assembly_file));
	    

	    while(character != -1)
		{
		    character = in.read();
		    if (character == 59) //;
			while(character != 10 && character != -1) //NL
			    {
				//System.out.println("sdlkfj " + (char)character);
				character = in.read(); //ignor comments
			    }
		    else
			{
			    file_contents[n] = (char)character;

			    if (file_contents[n] == (char)10)
				{
				    file_contents[n] = ' ';				    
				    file_contents[n+1] = '\n';
				    file_contents[n+2] = ' ';
				    n += 2;
				}
			    else if (!(('0' <= character && character <= '9') //0-9
				|| ('A' <= character && character <= 'Z') //A-Z
				|| ('a' <= character && character <= 'z') //a-z
				|| character == ' ' || character == '-')) //space or minus
				{			     
				    file_contents[n] = ' ';
				}
			     n++;	  
			     //System.out.print(file_contents[n-1]);

			} 
		}
		String contents = new String(file_contents);
		token_array = contents.split(" ");
		remove_empty_tokens();
		//System.out.println(token_array.length);
	}
	catch(Exception e){System.out.println(e); System.out.println("error reading files"); System.exit(1);}
    }

    static void create_linked_file(BufferedReader in, BufferedWriter out, String file)
    {
	char [] include = new char [256];
	String tmp;
	int line_number = 0;
	int character = 0;
	int n = 0;
	try 
	{
	    while(character != -1)
	    {
		while (character != 35 && character != -1) //until we find a # or eof - just write to temp file
		    {
			//System.out.println("wobble" + (char)character);
			out.write(character);
			character = in.read();
			if (character == 10) //NL
			    line_number++;
		    }
		if (character == 35) //#
		    {
			in.read(include, 0, 8);    //check for include statement
			tmp = new String(include);
			tmp = tmp.split(" ")[0];
			if(tmp.compareTo("include") == 0)
			    while (character != (int)(';') ) 
				{
				    character = in.read();  
				    if (character != (int)(';'))
					include[n] = (char)character; //read in filename
				    n++;
				    //System.out.println((char)character);
				}
			else
			    {
				System.out.println("error in " + file + ": include statement expected " + line_number + " found " + tmp);
				System.exit(0);
			    }
			tmp = new String(include);
			String [] filename = tmp.split(" ");
			
			create_linked_file(new BufferedReader(new FileReader(filename[0])), out, filename[0]);
		    }
	    }
	}
	catch(Exception e){System.out.println(e + " in " + file); System.exit(0);}
    }



    static void remove_empty_tokens()
    {
	int current = 0;
	for(int next = 0; next<token_array.length; next++)
	    if(token_array[next].length() != 0)
		{
		    //System.out.println(token_array[next]);
		    token_array[current] = token_array[next];
		    current++;
		    //System.out.println(next + " " + current);

		}
	
	token_array_length = current;
    }

    static void get_next_token()
    {
	token = token_array[token_index++].toLowerCase();
	while (token.charAt(0) == (char)10)
	    {	
		//System.out.println("x" + token + "x");

		token = token_array[token_index++].toLowerCase();
		line_num++;
	    }
	//if (token.length() < 10)
	//  System.out.println(token);
    }
    
    static short parse()
    {
	short ra, rb;
	short imm4, imm7, imm11;

	if (!label_detected)
	    instr_num++;
       
       	get_next_token();
	if(!pre_parse_in_progress && 
	   found_branch && (token.compareTo("br") == 0 || 
			    token.compareTo("bsr") == 0 ||
			    token.compareTo("ret") == 0 ))
	    System.out.print(" Warning - consecutive branch instructions");
	else
	    found_branch = false;

	if (!pre_parse_in_progress)
	    System.out.print("\n" + token);

	if (token.compareTo("or") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb = (short)(read_reg_index() << 8);
	    return (short)(0 + rb + ra);
	}
	else if (token.compareTo("and") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb = (short)(read_reg_index() << 8);
	    return (short)(1 + rb + ra);
	} 
	else if(token.compareTo("xor") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb = (short)(read_reg_index() << 8);
	    return (short)(2 + rb + ra);
	} 
	else if(token.compareTo("not") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    return (short)(3 + ra);
	} 
        else if(token.compareTo("mov") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token(); 
	    rb = (short)(read_reg_index() << 8);
	    return (short)(4 + rb + ra);
	}
        else if(token.compareTo("add") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb = (short)(read_reg_index() << 8);
	    return (short)(5 + rb + ra);
	} 
	else if(token.compareTo("sub") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb = (short)(read_reg_index() << 8);
	    return (short)(6 + rb + ra);
	} 
	else if(token.compareTo("cmp") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb = (short)(read_reg_index() << 8);
	    return (short)(7 + rb + ra);
	} 
	else if(token.compareTo("lsli") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm4 = (short)((16-read_imm4() & 0xf) << 8);
	    return (short)(8 + ra + imm4);
	} 
	else if(token.compareTo("lsri") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm4 = (short)(read_imm4() << 8);
	    return (short)(9 + ra + imm4);
	} 
        else if(token.compareTo("asri") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm4 = (short)(read_imm4() << 8);
	    return (short)(10 + ra + imm4);
	} 
        else if(token.compareTo("roti") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm4 = (short)((16-read_imm4() & 0xf) << 8);
	    return (short)(11 + ra + imm4);
	} 
        else if(token.compareTo("movi") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm7 = (short)(read_signed_imm7() << 5);
	    return (short)(12 + ra + imm7);
	} 
        else if(token.compareTo("addi") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm7 = (short)(read_unsigned_imm7() << 5);
	    return (short)(13 + ra + imm7);
	} 
        else if(token.compareTo("subi") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm7 = (short)(read_unsigned_imm7() << 5);
	    return (short)(14 + ra + imm7);
	} 
	else if(token.compareTo("cmpi") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    imm7 = (short)(read_signed_imm7() << 5);
	    return (short)(15 + ra + imm7);
	} 
        else if(token.compareTo("ld") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb =(short)( read_reg_index() << 8);
	    return (short)(16 + ra + rb);
	} 
        else if(token.compareTo("st") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    ra = (short)(read_reg_index() << 12);
	    get_next_token();
	    rb = (short)(read_reg_index() << 8);
	    return (short)(20 + ra + rb);		
	} 
	else if(token.compareTo("br") == 0) 
	{
	    label_detected = false;
	    found_branch = true;	    
	    get_next_token();
	    imm11 = (short)(get_relative_address(token) << 5);
	    return (short)(24 + imm11);
	} 
        else if(token.compareTo("bsr") == 0) 
	{
	    label_detected = false;
	    found_branch = true;
	    get_next_token();
	    imm11 = (short)(get_relative_address(token) << 5);
		return (short)(25 + imm11);
	} 
	else if(token.compareTo("ret") == 0) 
	{
	    label_detected = false;
	    found_branch = true;
	    return 26;
	} 
	else if(token.compareTo("ifs") == 0) 
	{
	    label_detected = false;
	    get_next_token();
	    imm4 = (short)(read_condition() << 8);
	    return (short)(31 + imm4);
	}			    
	
	else if(token.compareTo("nop") == 0)
	{
	    label_detected = false;
	    return 0;
	}
	else
	{
	    if(!label_detected)
		{
		    save_label();
		    label_detected = true;
		    return parse();
		}
	    else  //only one label per line
		{
		    System.out.println("error line " + line_num +" : instruction expected found " + token);
		    System.exit(0);
		}
	}
	return 0;
    }



    static short read_reg_index()
    {
	if (!pre_parse_in_progress)
	    System.out.print(" " + token + " ");

	short index = 0;
	try
	    {
		if (token.charAt(0) == (char)114) //r
		    index = Short.parseShort(token.split("r")[1]);
		else
		    throw new Exception("error line " + line_num + " " + instr_num + ": register index expected at " + token);
		if(index > ((1<<5) - 1))
		    throw new Exception("error line " + line_num + " : register index out of bounds");			
	    }
	catch(Exception e){System.out.println(e); System.exit(1);}
	//System.out.println(index);
	return index;
    }

    static short read_signed_imm7()
    {
	if (!pre_parse_in_progress)
	    System.out.print(" " + token + " ");

	short imm7 = 0;
	try
	    {
		imm7 = Short.parseShort(token);
		if (imm7 > ((1<<6) - 1) || imm7 < -(1<<6))
		    throw new Exception("error line " + line_num + " : signed 7 bit immediate out of bounds");
		else	
		    imm7 = (short)(imm7 & ((1<<7) - 1));
	    }
	catch(Exception e){System.out.println(e); System.exit(1);}
	return imm7;
    }

    static short read_unsigned_imm7()
    {
	if (!pre_parse_in_progress)
	    System.out.print(" " + token + " ");

	short imm7 = 0;
	try
	    {
		imm7 = Short.parseShort(token);
		if (imm7 > ((1<<7) - 1) || imm7 < 0)
		    throw new Exception("error line " + line_num + " : unsigned 7 bit immediate out of bounds");
	    }
	catch(Exception e){System.out.println(e); System.exit(1);}
	return imm7;
    }

    static short read_imm4()
    {
	System.out.print(" " + token + " ");

	short imm4 = 0;
	try
	    {
		imm4 = Short.parseShort(token);
		if (imm4 > ((1<<4) - 1))
		    throw new Exception("error line " + line_num + " : 4 bit immediate out of bounds");
	    }
	catch(Exception e){System.out.println(e); System.exit(1);}
	return imm4;
    }    
    static short read_condition()
    {
	short code = 0;
	try
	    {
		//System.out.println(token);
		if (token.compareTo("cc") == 0)  //condition prefix
		    {
			get_next_token();
			if (token.compareTo("nc") == 0 || token.compareTo("cc") == 0)
			    code = 0;
			else if (token.compareTo("c") == 0 || token.compareTo("cs") == 0) //carry
			    code = 1;
			else if (token.compareTo("nz") == 0 || token.compareTo("ne") == 0)
			    code = 2;
			else if (token.compareTo("z") == 0 || token.compareTo("eq") == 0) //zero 
			    code = 3;
			else if (token.compareTo("pl") == 0 || token.compareTo("p") == 0)
			    code = 4;
			else if (token.compareTo("mi") == 0 || token.compareTo("n") == 0) //negative
			    code = 5;
			else if (token.compareTo("lt") == 0)
			    code = 6;
			else if (token.compareTo("ge") == 0)
			    code = 7;
			else if (token.compareTo("gt") == 0)
			    code = 8;
			else if (token.compareTo("le") == 0)
			    code = 9;
			else if (token.compareTo("nv") == 0 || token.compareTo("vc") == 0)
			    code = 10;
			else if (token.compareTo("v") == 0 || token.compareTo("vs") == 0) //overflow
			    code = 11;
			else if (token.compareTo("hi") == 0)
			    code = 12;
			else if (token.compareTo("la") == 0)
			    code = 13;
			else
			    System.out.println("error line " + line_num + " : condition code expected");	

		    }
		else
		    System.out.println("error line " + line_num + " : condition expected");	
	    }
	catch(Exception e){System.out.println(e); System.exit(1);}
	return code;
    }

    static short read_imm11()
    {
	short imm11 = 0;
	try
	    {
		imm11 = Short.parseShort(token);
		if (imm11 > ((1<<12) - 1))
		    throw new Exception("error line " + line_num + " : signed 11 bit immediate out of bounds");
	    }
	catch(Exception e){System.out.println(e); System.exit(1);}
	return imm11;
    }

    static void parse_and_write(String destination_file)
    {
	String hex_instruction;
	try
	{
	    BufferedWriter out = new BufferedWriter(new FileWriter(destination_file));
	    out.write("@00000000\n", 0, 10); //stuff
	    out.write("0000 ", 0, 5);
	    char parsed;
	    while(token_index < token_array_length - 3)
		{
		    parsed = (char)parse();
		    hex_instruction = convert_to_hex(parsed);
		    out.write(hex_instruction, 0, 4);
		    out.write(" ", 0, 1);
		    System.out.print("\u0009" + hex_instruction + " " + instr_num * 2);
		}
	    out.write(" 0000 ", 0, 6);
	    out.close();
	}
	catch(Exception e){System.out.println(e); System.out.println("error writing data file : line " + line_num); System.exit(1);}
    }

    static String convert_to_hex(char instruction)
    {
	char [] nibble = new char [4];
	nibble[0] = (char)(instruction >> 12);
	nibble[1] = (char)((instruction >> 8) & ((1<<4) -1));
	nibble[2] = (char)((instruction >> 4) & ((1<<4) -1));
	nibble[3] = (char)(instruction & ((1<<4) -1));
	
      	for(int i = 0; i<4; i++)
	    {
		//System.out.println((int)nibble[i]);

		if (nibble[i] < 10)
		    nibble[i] += 48;
		else 
		    nibble[i] += 87;
	    
	    }   
	String hex_instruction = new String(nibble);
       	return hex_instruction;
    }
 
    static Label first;
    static Label last;

    static void save_label()
    {
	if(pre_parse_in_progress) //only save labels durring the preparse stage
	    if (first == null)
		{
		    first = new Label(token, instr_num, null);
		    last = first;
		}
	    else 
		{
		    last.next = new Label(token, instr_num, null);
		    last = last.next;
		}
    }

    static short get_relative_address(String branch_label)
    {
	Label current = first;
	short label_loc;

	if (pre_parse_in_progress)
	    return 0;
	else
	{
	    if (current == null)
		{
		    System.out.println("error line " + line_num + ": invalid label");
		System.exit(0);
		}
	    else
		{
		    while(current.label.compareTo(branch_label) != 0)
			{
			    if (current == last)
				{
				    System.out.println("error line " + line_num + ": invalid label");
				    System.exit(0);
				}
			    else
				current = current.next;
			}
		    return (short)(current.location - instr_num - 1);
		}
	    return -1;
	}
    }

    static void pre_parse_labels()
    {
	pre_parse_in_progress = true;
	while(token_index < token_array_length - 3)
	    parse();
	pre_parse_in_progress = false;
	line_num = 0;
	instr_num = 0;
	token_index = 0;
    }
}














