/*

THIS SOFTWARE IS DISTRIBUTED UNDER GPL LICENSE

Copyright (C) 2006  Lukasz Grzegorz Maciak
http://terminally-incoherent.com

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA or visit http://www.gnu.org/licenses/gpl.txt.

*/



/*
 * Created on Feb 20, 2005
 *
 * Updated of Apr 16, 2005
 *
 * Changes:
 *
 * 		- added new overloaded constructor
 * 		- changed the scope of display method to public
 *
 */


import java.io.*;
import java.util.*;

/**
 * @author Lukasz Grzegorz Maciak
 *
 */
public class AssemblerPass1
{
	// compiler command line static paramerers - these are used
	// only when the class is called from the command line via
	// the main function
	private static boolean verbose = false; // if set more output
	private static boolean debug = false; // switches on printing stack traces on exceptions
	private static boolean show_sym = false; // if set display symtab
	private static boolean show_op = false; // if set display optab

	private static String lang_def = "sic.op"; // the default loc of language def file
	private static int input_at = 0; // iternal use - where is input file in cmd param list


	// ### Object Vars ###

	//	language definition - note hat java.util.Properties is derived
	//	from the Hashtable class so this is essentially a modified hashtable
	private Properties OPTAB;

	//	SYMTAB is implemented using java.util.Hashtable class
	private Hashtable SYMTAB;

	// IN is the name of the program file to be assembled
	// LANG is the name of the file where the opcode values are defined
	private final String IN, LANG;

	// program length after 1st pass
	private int length;

	/**
	 * Main Class with which you initialize the program
	 *
	 * @param args - command line arguments
	 */
	public static void main(String[] args)
	{
		System.out.println("\nSIC Assembler: Pass 1");

		// display usage if the class is called with no arguments
		if(args.length <= 0 )
		{
			System.out.println("\nUsage: AssemblerPass1 [-vdlos] [lang_def.op] program.sic\n");
			System.out.println("\t[v]\t verbose (display all debuging info)");
			System.out.println("\t[d]\t debug mode - very verbose (verbose mode + print stack traces on exceptions)");
			System.out.println("\t[l]\t load different language file specified as first argument (program file goes second)");
			System.out.println("\t[o]\t display OPTAB");
			System.out.println("\t[s]\t display SYMTAB");

			System.exit(0);
		}

		// if the argument starts with - then parse the argument list for
		// special flags and indicators
		if(args[0].charAt(0) == '-')
				params(args);

		long st = System.currentTimeMillis(); // measure time
		AssemblerPass1 p1 = new AssemblerPass1(args[input_at], lang_def);

		System.out.println("\nPass 1 finished in " + (System.currentTimeMillis() - st) + " milliseconds");

		// If flags are set print tables to stdout

		if(show_op)
		{
			System.out.println("\nDisplaying the OPTAB:\n");
			display(p1.getOp());
		}

		if(show_sym)
		{
			System.out.println("\nDisplaying the SYMTAB:\n");
			display(p1.getSym());
		}

	}

	/**
	 * Parses the command line arguments and sets the static flags
	 * which enable certain modes such as debug, verbose etc...
	 *
	 * @param params - array of all the command line arguments
	 */
	private static void params(String[] params)
	{
		for(int i=0; i <= params[0].length()-1; i++)
		{
			switch(params[0].charAt(i))
			{
				case '-':
					input_at = 1;
					break;
				case 'v':
					System.out.println("Verbose flag set...");
					verbose = true;
					break;
				case 'l': // setting nonstandard language def file
					System.out.println("Using nonstandard language definition...");
					lang_def = params[1]; // set the lang def file to user specified
					print("lang_def set to " + params[1]);
					input_at = 2; // tell the compiler to get the second filename as input
					break;
				case 'o':
					System.out.println("OPTAB display flag set...");
					show_op = true;
					break;
				case 's':
					System.out.println("SYMTAB display flag set...");
					show_sym = true;
					break;
				case 'd':
					System.out.println("Debug flag set...");
					debug = true;
					verbose = true;
					break;

			}
		}
	}

	/**
	 * This is an alternative print function used for debuging
	 * All non essential debug output is chaneled through this
	 * method and printed only when verbose flag is set
	 *
	 * @param print text to be printed to stdout
	 */
	private static void print(String print)
	{
		if(verbose) System.out.println("\tv:\t" + print);
	}

	/**
	 * Dumps the hash table in a nice tabber format to the stdout
	 *
	 * @param table hash table to be displayed
	 */
	public static void display(Hashtable table)
	{
		Enumeration keys = table.keys();
		Enumeration values = table.elements();

		while(keys.hasMoreElements())
		{
			System.out.println("\t" + keys.nextElement() + "\t\t" + values.nextElement());
		}

	}

	// ################## NONSTATIC STUFF STARTS HERE ######################


	/**
	 * Constructor...
	 */
	public AssemblerPass1(String input, String langdef)
	{
		IN = input;
		LANG = langdef;

		genOPTAB(); 	// generate OPTAB
		genSYMTAB();	// generate SYMTAB
	}

	public AssemblerPass1(String input, String langdef, boolean verbose, boolean debug, boolean sym, boolean op)
	{
		IN = input;
		LANG = langdef;

		AssemblerPass1.verbose = verbose;
		AssemblerPass1.debug = debug;
		show_sym = sym;
		show_op = op;

		genOPTAB(); 	// generate OPTAB
		genSYMTAB();	// generate SYMTAB
	}

	/**
	 * Generates the OPTAB by using the language definition file
	 * specified in LANG
	 *
	 * Please note that OPTAB is a java Properties class which is in
	 * a sense a Hashtable with additional functionality (such as automatic
	 * parsing of the input file on loading).
	 *
	 */
	private void genOPTAB()
	{
		print("Reading the language definition file...");
		print("LANG (language definition file) is set to: " + LANG);

		print("Creating hashtable for OPTAB..");
		OPTAB = new Properties();

		try
		{
			print("Loading the values from LANG file into hashtable");

			// load properties from the LANG file
			OPTAB.load(new FileInputStream(new File(LANG)));

			print("OPTAB generated sucessfuly.");

			// if debug flag is set dump the OPTAB into stdout
			if(debug)
				OPTAB.list(System.out);

		}
		catch(IOException ioe)
		{
			if(!debug)
				System.out.println("An error has occured while reading the language definition file:" + ioe.getMessage());
			else
				ioe.printStackTrace();
		}

	}

	/**
	 * Generating the SYMTAB using the algorithm from the book
	 *
	 */
	private void genSYMTAB()
	{
		print("Preparing to read the source file");
		print("Source file is: " + IN);

		print("Declaring the LOCCTR, and temporary variables");
		int LOCCTR, start = 0; 	// LOCCTR initialized to 0 - this will be adjusted later
		String label, op, arg; 	// temp vars used to hold current values in the loop

		print("Generating empty SYMTAB hashtable");
		SYMTAB = new Hashtable(); // need to initialize this before we go into the loop

		try
		{
			print("Opening the source file...");
			BufferedReader src = new BufferedReader(new FileReader(IN));

			print("Initializing tokenizer object to parse out text.");
			StringTokenizer line;

			print("Reading the 1st line of the source file...");
			String inline = src.readLine();
			line = new StringTokenizer(inline);	// tokenized uses whitespace as delimiters

			// this variable is used to temporarily hold the first token read on the
			// line. Since 1st token can be either a '.' (dot - comment), a label or an opcode
			// we need to evaluate it first before assigning it to either op or label
			String tmp;

			int next; // next line to read is ...

			if(line.countTokens()<3)
			{
				op = line.nextToken();
				arg = line.nextToken();
			}
			else
			{
				label = line.nextToken();
				op = line.nextToken();
				arg = line.nextToken();
			}

			if(op.toUpperCase().equals("START"))
			{
				print("Found START directive. Setting LOCCTR.");
				start = Integer.parseInt(arg, 16); // assigning the start address to start (must be in hex)
				LOCCTR = start;

				//write?

				print("Reading a line of the source file");
				line = new StringTokenizer(src.readLine());  // read new line
				next = 2;

			}
			else
			{
				print("No START directive on line 1.");
				LOCCTR = 0;
				line = new StringTokenizer(inline); // reread the 1st line into the tokenizer
				next = 1;
			}

			int l = next; // source line counter (start at 2 - line 1 was read above)
			int e = 0; // error counter

			// this loop will be broken when current op value is END
			// if there is no op value the exception will be thrown
			while(true)
			{
				print("-- Parsing line " + l + "\t\t\tLOCCTR: " + Integer.toHexString(LOCCTR));

				tmp = line.nextToken();

				if(!tmp.equals("."))
				{
					print("Line " + l + " is not a comment");

					if(line.countTokens()<2)
					{
						label = null;
						op = tmp;

						if(line.hasMoreTokens())
							arg = line.nextToken();
						else
							arg = null;
					}
					else
					{
						label = tmp;
						op = line.nextToken();

						if(line.hasMoreTokens())
							arg = line.nextToken();
						else
							arg = null;
					}

					print(" * Parsing results: \t\t\t\tLABEL=" + label+ "\tOP=" + op + "\tARG=" + arg);

					if(op.toUpperCase().equals("END"))
					{
						print("END directive found on line " + l + " - exiting");
						break;
					}

					if(label != null)
					{
						print("LABEL found on line " + l + "  - searching SYMTAB..");

						if(SYMTAB.containsKey(label.toUpperCase()))
						{
							System.out.println("ERROR: duplicate LABEL on line " + l);
							e++;
						}
						else
						{
							print("LABEL not in SYMTAB - inserting..");
							SYMTAB.put(label, Integer.toHexString(LOCCTR));
						}
					}

					print("Analyzing OPCODE on line " + l);

					if(OPTAB.containsKey(op))
					{

						print("OPCODE found in OPTAB - incrementing LOCCTR");
						LOCCTR += 3;
					}
					else if(op.toUpperCase().equals("WORD"))
					{
						print("Special OPCODE Reserving a word");
						LOCCTR += 3;
					}
					else if(op.toUpperCase().equals("RESW"))
					{
						print("OPCODE is specoal - reserving " + arg + " words");
						LOCCTR += 3 * Integer.parseInt(arg);
					}
					else if(op.toUpperCase().equals("RESB"))
					{
						print("OPCODE is specoal - reserving " + arg + " bytes");
						LOCCTR += Integer.parseInt(arg);
					}
					else if(op.toUpperCase().equals("BYTE"))
					{
						// the byte is usually given in form C'SOMETHING' or X'11' etc..
						// or in other words letter + quote + value + quote - so I
						// subtract 3 chars from length to just ge the value of C

						// For X I'm stil counting 3 waste chars and then each char
						// inside is a nybble (4 bit hex value). Since you can usually
						// only reserve a byte, then the length can be calculated as
						// ceiling((total_length -3)/2)

						int b; // length of the constant in bytes

						if(arg.toUpperCase().charAt(0) == 'C')
							b = arg.length()-3;
						else
							b = (int) Math.ceil((arg.length()-3)/2);

						print("OPCODE is special - reserving " + b + " bytes");
						LOCCTR += b;
					}
					else
					{
						System.out.println("ERROR: invalid operation code on line " + l + " :: " + op + " is undefined");
						e++;
					}
				}
				//write?

				print("Reading a line of the source file");
				line = new StringTokenizer(src.readLine());
				l++; // increment the line count

			}

			length = LOCCTR - start;
			print("Calculating the program length for " + l + " lines: " + length);

			System.out.println("\nDone. [" + l + " lines] [" + length + " bytes] [" + e + " errors]");



		}
		catch(IOException ioe)
		{
			if(!debug)
				System.out.println("An error has occured while reading the program source file:" + ioe.getMessage());
			else
				ioe.printStackTrace();
		}
	}


	/**
	 * Returns OPTAB as a Hashtable object. Please note that it still
	 * retains all the features of Properties object, so you can re-cast
	 * it into Properties type.
	 *
	 * I'm returning it as a Hashtable to conform to project requirements
	 * which state that both OPTAB and SYMTAB need to be hashtables.
	 *
	 * @return Hashtable OPTAB
	 */
	public Hashtable getOp()
	{
		return OPTAB;
	}

	public Hashtable getSym()
	{
		return SYMTAB;
	}

}
