/* LexicalA.java - lexikalni analyzator
 *
 * Michal Beran, berny@students.zcu.cz
 * 4.1.2001
 */

package pl0prog;

import java.util.*;
import java.io.*;

/** Trida LexicalA provadi lexikalni analyzu predlozeneho souboru spolu se
  * zajistenim I/O operacich.
  */
public class LexicalA {

	/** Naposledy nacteny symbol */
	private int CurrentSymbol;
	/** Naposledy nactene cislo */
	private int CurrentNumber;
	/** Pouzity spravce souboru */
	private SourceReader Source;
	/** Naposledy nacteny identifikator */
	private StringBuffer CurrentIdentifier = new StringBuffer();
	/** Naposledy nacteny znak */
	private char CurrentChar = ' ';
	/** Hashtabulka pro jednomistne symbolu */
	private Hashtable SingleSymbols = new Hashtable();
	/** Hashtabulka pro vicemistne symboly */
	private Hashtable KeyWords = new Hashtable();

	/** Ziskani nacteneho symbolu.
	  * @return nacteny symbol
	  */
	public int getCurrentSymbol() {return CurrentSymbol; };

	/** Ziskani nacteneho cisla.
	  * @return nacteny cisla
	  */
	public int getCurrentNumber() {return CurrentNumber; };

	/** Ziskani nacteneho znaku.
	  * @return nacteny znaku.
	  */
	public char getCurrentChar() {return CurrentChar; };	

	/** Ziskani nacteneho identifikatoru.
	  * @return nacteny identifikator
	  */
	public String getCurrentIdentifier() {return CurrentIdentifier.toString();};	

	/** Zobrazi cislo chyby na miste v radku, kde se tato chyba vyskytla 
	  * @param cislo chyby.
	  */
	public void writeError(int ErrorType) {
		int i;
		for (i=0;i<Source.CurrentIndex-1;i++) 
			System.out.print(" ");
		System.out.println(ErrorType);
	}

    /* Konstruktor lexikalniho analyzatoru, zpusobuje IOException.
	 * @param path jmeno souboru se zdrojovym textem programu.
	 */
	public LexicalA(String path) throws IOException{

		try {
			Source = new SourceReader(path);
		} catch (IOException e) {
			throw e;
		}

		SingleSymbols.put(new Character('+'), new Integer(SetSym.PLUS));
		SingleSymbols.put(new Character('-'), new Integer(SetSym.MINUS));
		SingleSymbols.put(new Character('*'), new Integer(SetSym.TIMES));
		SingleSymbols.put(new Character('/'), new Integer(SetSym.SLASH));
		SingleSymbols.put(new Character('%'), new Integer(SetSym.MODULO));
		SingleSymbols.put(new Character('('), new Integer(SetSym.LPAREN));
		SingleSymbols.put(new Character(')'), new Integer(SetSym.RPAREN));
		SingleSymbols.put(new Character('='), new Integer(SetSym.EQL));
		SingleSymbols.put(new Character(','), new Integer(SetSym.COMMA));
		SingleSymbols.put(new Character('.'), new Integer(SetSym.PERIOD));
		SingleSymbols.put(new Character('#'), new Integer(SetSym.NEQ));
		SingleSymbols.put(new Character('<'), new Integer(SetSym.LSS));
		SingleSymbols.put(new Character('>'), new Integer(SetSym.GTR));
		SingleSymbols.put(new Character(';'), new Integer(SetSym.SEMICOLON));

		KeyWords.put(new String("begin"), new Integer(SetSym.BEGINSYM));
		KeyWords.put(new String("call"), new Integer(SetSym.CALLSYM));
		KeyWords.put(new String("const"), new Integer(SetSym.CONSTSYM));
		KeyWords.put(new String("do"), new Integer(SetSym.DOSYM));
		KeyWords.put(new String("end"), new Integer(SetSym.ENDSYM));
		KeyWords.put(new String("if"), new Integer(SetSym.IFSYM));
		KeyWords.put(new String("odd"), new Integer(SetSym.ODDSYM));
		KeyWords.put(new String("procedure"), new Integer(SetSym.PROCSYM));
		KeyWords.put(new String("then"), new Integer(SetSym.THENSYM));
		KeyWords.put(new String("var"), new Integer(SetSym.VARSYM));
		KeyWords.put(new String("while"), new Integer(SetSym.WHILESYM));

		getNextSymbol(); // automaticke nacteni prvniho znaku
	}

	/** Vynulovani naposledy nacteneho cisla - pouziva se pri prekroceni
	  povoleneho rozsahu cisel.
	  */
	public void zeroNumber() {
		CurrentNumber = 0;
	}

	/** Provede nacteni dalsiho symbolu (ten je mozne ziskat pomoci metody 
	  * getCurrentSymbol() */
	public void getNextSymbol() {
		while (CurrentChar<=' ') 
			CurrentChar = Source.getChar(); /*** Nacteni jednoho znaku ***/
		// nacteni identifikatoru do CurrentIdentifier
		if (Character.isLetter(CurrentChar)) {   /*identifier or reserved word*/
			CurrentIdentifier=new StringBuffer();
			do {
				CurrentIdentifier.append(new Character(CurrentChar)); 
			  	CurrentChar = Source.getChar(); /*** Nacteni jednoho znaku ***/
			} while (Character.isLetterOrDigit(CurrentChar));

			if (KeyWords.containsKey(CurrentIdentifier.toString()) == true)
				CurrentSymbol = ((Integer)KeyWords.get(CurrentIdentifier.toString())).intValue();
			else 
				CurrentSymbol = SetSym.IDENT;
		} // identifier or reserved word
		else if (Character.isDigit(CurrentChar)) {   /* number */
			CurrentSymbol = SetSym.NUMBER;
			CurrentNumber = 0;
			do { /* get value */
				CurrentNumber=10*CurrentNumber+(CurrentChar-'0');
				CurrentChar = Source.getChar();
			} while (Character.isDigit(CurrentChar));
		} /* number */
		else if (CurrentChar==':') { /* muze jit o prirazeni */
			CurrentChar = Source.getChar();
			if (CurrentChar=='=') {
				CurrentSymbol = SetSym.BECOMES; 
				CurrentChar = Source.getChar();
		  	} 
			else 
				CurrentSymbol = SetSym.NULL;
		} 
		else if (CurrentChar=='<') {
			CurrentChar = Source.getChar();
			if (CurrentChar=='=') {
				CurrentSymbol=SetSym.LEQ; /* mensi nebo rovno */
				CurrentChar = Source.getChar();
			} 
			else 
				CurrentSymbol = SetSym.LSS; /* mensi nez */
		} 
		else if (CurrentChar=='>') {
			CurrentChar = Source.getChar();
			if (CurrentChar=='=') {
				CurrentSymbol=SetSym.GEQ;	/* vetsi nebo rovno */
				CurrentChar = Source.getChar();
		  	} 
			else 
				CurrentSymbol=SetSym.GTR; /* vetsi nez */
		} // identifier or reserved word
		else {
			if (SingleSymbols.containsKey(new Character(CurrentChar)) == true) 
			{
				CurrentSymbol = ((Integer)SingleSymbols.get(
					new Character(CurrentChar))
					).intValue();
				CurrentChar = Source.getChar();	
			}
			else 
			{
				CurrentSymbol = SetSym.NULL;
				CurrentChar = Source.getChar();
			}
			
		}		
	}


	/** Podtrida zajistujici praci se souborem */
	class SourceReader {
		private FileReader fileReader;
		private BufferedReader file;
		/* Aktualni radka programu */
		private String CurrentLine = "";
		/* Aktualni offset vzhledem k zacatku radky */
		int CurrentIndex = 0;
	
		/** Zavedeni nove instance tridy SourceReader; zpusobuje vyjimku
		  * IOException.
		  * @param fileName jmeno souboru se zdrojovym textem
		  */

		public SourceReader (String fileName) throws IOException {
			try {
			  	fileReader = new FileReader(fileName);
				file = new BufferedReader(fileReader);
			}
			catch (FileNotFoundException e) {
				System.out.println("Soubor "+fileName+" nenalezen.");
				throw e;
			}
		}
		/** Ziskani dalsiho znaku ze souboru 
		  * @return vraceny znak.
		  */  	
		public char getChar() {
			if (CurrentIndex == CurrentLine.length()) {
				try {
					if ((CurrentLine = file.readLine()) == null) {
						System.out.println("Program neni kompletni nebo " + 
						"chybi tecka za koncovym end.");
						throw new ErrorsException();
					}
					else {
						CurrentLine = new String(CurrentLine + " "); 
						CurrentIndex = 0;
						System.out.println(CurrentLine);
						return CurrentLine.charAt(CurrentIndex++);
					}
				}
				catch (IOException e) {
					System.out.println("Chyba pri cteni ze souboru.");
					return 0;
				}
			}
			else 
			{
				return CurrentLine.charAt(CurrentIndex++);
			}
		}

		/* Destruktor tridy */
		protected void finalize() throws Throwable {
			try {
				if (fileReader != null)
					fileReader.close();
			}
			catch (IOException ee) {
				System.exit(2);
			}
			super.finalize();
		}
	} // end of SourceReader
	
} // end of LexicalA