/*****************************************************************************

  Project:	STACK-MACHINE SIMULATOR
  Filename:	StackMachine.java
  Written by:	Ettore Pasquini, 1999

Questo modulo fornisce la descrizione dell'architettura della stack-machine. L'idea principale è quella di definire la macchina come un contenitore di oggetti, quali registri, Alu, stack, ecc., e un set di istruzioni (metodi), che specificano come sono usati i componenti "hardware". In questo modo è come se ogni istruzione definisse una parte della logica di controllo e le opportune interconnessioni. 
Accanto alla CPU vi devono ovviamente essere altre entità (esterne): la memoria, divisa in CODE SEGMENT, DATA SEGMENT, STACK SEGMENT, e ovviamente un programma. La macchina conterrà dei riferimenti opportuni (sulla falsariga dei registri CS, DS, SS dell'architettura i80x86).

*****************************************************************************/

import java.util.*;
import CodeSegment;
import DataSegment;
import MachineSignal;

abstract class Machine{
  protected ProgramCounter PC;
  protected InstructionRegister IR;
  protected CodeSegment CS;
  protected DataSegment DS;

  public abstract void init();
  public abstract void go()       		   throws MachineSignal;
  protected abstract void fetch() 		   throws MachineException;
  protected abstract void execute( Instruction i ) throws MachineSignal;
  public ProgramCounter PC()			  { return PC; }
  public CodeSegment getCodeSegment()		  { return CS; }
  public DataSegment getDataSegment()		  { return DS; }
  public void setCodeSegment( CodeSegment CSref ) { CS= CSref;  }
  public void setDataSegment( DataSegment DSref ) { DS= DSref;  }
  public String machineName()			  { return toString(); }
}//Machine


public class StackMachine extends Machine{
  protected Register TR1, TR2, TR3;	//Temporary Registers
  protected FlagRegister FR;
  protected IntStack SR;		//Lo stack principale
  protected Alu ALU1;
  private String name;

  public StackMachine( String nomeMacchina, String programstring ){
	PC   = new ProgramCounter();
	FR   = new FlagRegister();
	TR1  = new Register();
	TR2  = new Register();
	TR3  = new Register();
	ALU1 = new Alu();
	SR   = new IntStack();
	CS   = new CodeSegment();
	DS   = new DataSegment();
	caricaCSDS( programstring );
	IR   = new InstructionRegister( CS, PC );
	name = nomeMacchina;
  }

  public void init(){
	//pone la macchina nello stato iniziale, pronta per
	//iniziare una nuova computazione (con lo stesso prg.)
	PC.reset();
	FR.reset();
	TR1.reset();
	TR2.reset();
	TR3.reset();
	ALU1.reset();
	SR.reset();
	DS.init();
	IR.reset(CS, PC);
  }

  public void go() throws MachineSignal{
	go( false );
  }

  public void go( boolean viewstack ) throws MachineSignal{
	Instruction i;
	try{
	     do{
		if (viewstack)
		   System.out.println( "Stack queue: " + SR.stringView());
		fetch();
		i = IR.contents();
		execute(i);
	     }while( true );

	}catch( HaltMessage he ){
		throw he;
	}catch( HaltNotFoundException hnfe ){
		System.out.println("* HALT not found *");
		throw hnfe;
	}catch( InvalidDataReadAddressException idrae ){
		System.out.println("* Error while reading data from the DS *");
		throw idrae;
	}catch( InvalidDataWriteAddressException idwae ){
		System.out.println("* Error while writing data from the DS *");
		throw idwae;
	}catch( EmptyMachineStackException ese ){
		System.out.println( "* In " + getClass().getName() + " " 
		   + machineName()+" stack was found empty when shouldn\'t *"); 
		throw ese;
	}catch( NotRecognizedInstructionException nrie ){
		System.out.println( "* Unknown instruction found in " +
		  getClass().getName() + machineName() + " *" );
		throw nrie;
	}finally{}
  }


  /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>      */
  /* >>>>> Ogni istruzione macchina può far uso di alcune
           microistruzioni di uso generale: pertanto esse 
           saranno "cablate" in moduli separati, non
           accessibili dall'esterno			 <<<<< */
  /*       <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */

  protected void fetch() throws HaltNotFoundException{
	try{
		IR.setContents( CS.read(PC) );
	}catch( InvalidCodeReadAddressException icrae ){
		throw new HaltNotFoundException();
	}
  }

  protected void execute( Instruction i ) throws MachineSignal{
	i.exec( this );
  }

  protected void load(Register R, int val)    { R.setContents(val); }  

  protected void indload(Register R, int addr) throws
					InvalidDataReadAddressException{

	/** 	carica su R il valore contenuto all'indirizzo 'addr'.
	 * 	(indiriz. indiretto)
	 */

	TR1.setContents(addr);
	R.setContents(read(TR1));
  }

  protected int  read(Register TR) throws InvalidDataReadAddressException{ 
	return DS.read(TR);
  }

  protected void write(Register TR1, Register TR2) throws 
					InvalidDataWriteAddressException{
	DS.write(TR1, TR2);
  }

  /**
   *    >>>>>>>>>>>>>>>>>>>>>          *
   *    >>>>> INSTRUCTION SET <<<<<    *
   *          <<<<<<<<<<<<<<<<<<<<<    *
   */


  public void PUSH(int addr) throws InvalidDataReadAddressException{
	// legge dalla mem. il valore contenuto all'ind. 'addr' e lo mette 
	// sullo stack (indirizzamento indiretto)
 	indload(TR3, addr);	//carica TR3
	SR.push(TR3);
	PC.inc();
  }

  public void PUSHC(int val){
	//mette sullo stack la costante specificata (ind. immediato)
	load(TR3, val);
	SR.push(TR3);
	PC.inc();
  }

  public void PUSHADD(int addr){
	// mette sullo stack l'indir. 'addr'
	load(TR3, addr);
	SR.push(TR3);
	PC.inc();
  }

  public void POP() throws EmptyMachineStackException {
	SR.pop(TR1);	//discard a value
	PC.inc();
  }

  public void STORE() throws EmptyMachineStackException,
			     InvalidDataWriteAddressException{
	// fa 2 pop: la prima dà il valore da memorizzare, la seconda
	// l'indirizzo in cui scrivere
	SR.pop(TR1);
	SR.pop(TR2);
	write(TR1,TR2);
	PC.inc();
  }

  public void ADD() throws EmptyMachineStackException{
	// fa 2 pop per leggere  gli operandi (in ordine inverso), aziona
	// la ALU e fa la push del risultato
	SR.pop(TR2);
	SR.pop(TR1);
	ALU1.enable(Alu.add, TR1, TR2, TR3);
	FR.setZ(TR3);
	FR.setN(TR3);
	SR.push(TR3);
	PC.inc();
  }

  public void SUB() throws EmptyMachineStackException{
	SR.pop(TR2);
	SR.pop(TR1);
	ALU1.enable(Alu.sub, TR1, TR2, TR3);
	FR.setZ(TR3);
	FR.setN(TR3);
	SR.push(TR3);
	PC.inc();
  }

  public void MUL() throws EmptyMachineStackException{
	SR.pop(TR2);
	SR.pop(TR1);
	ALU1.enable(Alu.mul, TR1, TR2, TR3);
	FR.setZ(TR3);
	FR.setN(TR3);
	SR.push(TR3);
	PC.inc();
  }

  public void DIV() throws EmptyMachineStackException{
	SR.pop(TR2);
	SR.pop(TR1);
	ALU1.enable(Alu.div, TR1, TR2, TR3);
	FR.setZ(TR3);
	FR.setN(TR3);
	SR.push(TR3);
	PC.inc();
  }

  public void AND() throws EmptyMachineStackException{
	SR.pop(TR2);
	SR.pop(TR1);
	ALU1.enable(Alu.and, TR1, TR2, TR3);
	FR.setZ(TR3);
	SR.push(TR3);
	PC.inc();
  }

  public void OR() throws EmptyMachineStackException{
	SR.pop(TR2);
	SR.pop(TR1);
	ALU1.enable(Alu.or, TR1, TR2, TR3);
	FR.setZ(TR3);
	SR.push(TR3);
	PC.inc();
  }

  public void IMPL() throws EmptyMachineStackException{
	SR.pop(TR2);
	SR.pop(TR1);
	ALU1.enable(Alu.impl, TR1, TR2, TR3);
	FR.setZ(TR3);
	SR.push(TR3);
	PC.inc();
  }

  public void JUMP(int addr){
	PC.setContents(addr);
  }

  public void JUMPLE(int addr){
	if ( FR.N() || FR.Z() ) PC.setContents(addr);
	else PC.inc();
  }

  public void JUMPLT(int addr){
	if ( FR.N() ) PC.setContents(addr);
	else PC.inc();
  }

  public void JUMPGE(int addr){
	if ( !FR.N() || FR.Z() ) PC.setContents(addr);
	else PC.inc();
  }

  public void JUMPGT(int addr){
	if ( !FR.N() ) PC.setContents(addr);
	else PC.inc();
  }

  public void JUMPEQ(int addr){
	if ( FR.Z() ) PC.setContents(addr);
	else PC.inc();
  }

  public void JUMPNE(int addr){
	if ( !FR.Z() ) PC.setContents(addr);
	else PC.inc();
  }

  public void NOP(){ PC.inc(); }

  public void HALT() throws HaltMessage{ throw new HaltMessage();}

	/* * * */

  public void     setStackSegment( IntStack SRref )  { SR = SRref; }

  public IntStack getStackSegment()                  { return SR;  }

  public String   machineName()                      { return name;}

  public String   rapportoDOS(){
	return "Rapporto dello stato interno della " + 
		getClass().getName() + " "  + machineName() +
		":\n  IR= " +IR.toString()  + 
		"\tPC= "  +PC.toString()  + "\tTR1= " +TR1.toString()  + 
		"\tTR2= " +TR2.toString() + "\tTR3= " +TR3.toString()  + 
		"\tFR= "  +FR.toString()  + "\n  ALU= " +ALU1.toString() + 
		"\tStack: " +SR.stringView() ;
  }

  public String	  rapportoDOS( boolean showCS, boolean showDS ){
	return rapportoDOS() + (showCS ? CS.toString() : "") + 
			    (showDS ? DS.toString() : "");
  }

  public String   rapportoV(){
	return  getClass().getName() + ": "  + machineName() +
		"\n IR= " +IR.toString()  + 
		"\n PC= "  +PC.toString()  + "\n TR1= " +TR1.toString()  + 
		"\n TR2= " +TR2.toString() + "\n TR3= " +TR3.toString()  + 
		"\n FR= "  +FR.toString()  + "\n ALU= " +ALU1.toString() + 
		"\n Stack: " +SR.stringViewV() + "\n";
  }

  protected Instruction makeInstruction( String opcode, int[] operands){
	/** 
	  *   ogni Machine ridefinirà questo metodo ogni qual volta venga 
	  *   espanso o modificato il set di istruzioni
	  */
	return StackMachineInstruction.make( opcode, operands);
  }

  public void loadNewProgram( String programstring ){
	caricaCSDS( programstring );
	PC.reset();
	IR.reset(CS, PC);
  }

  protected void caricaCSDS( String prg_str ) /*throws parserIOException*/{
	String TOKEN_DELIMITERS = "\n \t \r , ( ) -";
	String ESMC_BLOCK_FUNCTOR = "datablock";
	StringTokenizer t=new StringTokenizer(prg_str,TOKEN_DELIMITERS,false);
	int[] operands= new int[4];
	CodeVector pv= CS.getCodeVector();
	DataVector dv= DS.getDataVector();
	pv.removeAllElements();
	dv.removeAllElements();
	CS.setBase(0);
	DS.setBase(0);
	if ( !(t.hasMoreTokens()) ){
		System.out.println( "ESMC lexical analysis report: no input.");
		pv.addElement( StackMachineInstruction.make("halt",operands));
		return;
	}
	int i=0;
	int lines=0;
	String s, op="";
	/* prendo il 1o opcode */
	s = t.nextToken();
	boolean raggiunto_nuova_istr;
	// boolean raggiunto_block = = (o.equals( ESMC_BLOCK_FUNCTOR ));
	while( !(op.equals( ESMC_BLOCK_FUNCTOR)) ) {
		i=0;
		/* ora prendo gli operandi */
		raggiunto_nuova_istr = false;
		while( t.hasMoreTokens() && !raggiunto_nuova_istr){
			op= t.nextToken();
			/* o è un numero, o è una stringa */
			try{
				operands[i]= Integer.parseInt(op);
			}catch(NumberFormatException nfe){
				raggiunto_nuova_istr = true;
			}
			i++;
		}
		if ( !raggiunto_nuova_istr ){
			//in questo caso sono uscito dal while perchè non 
			//c'erano + token,non perchè ho incontrato una stringa
			System.out.println("ESMC Lexical analysis error: " + 
				"block statement missing." );	
			pv.removeAllElements();
			pv.addElement( StackMachineInstruction.make( "halt", 
								operands));
			return;
		}
		lines++;
		Instruction instr= makeInstruction(s, operands);
		if (instr instanceof NotRecognizedInstruction){
			System.out.println( "ESMC Lexical analysis error: " +
						"unknown Instruction "+ s);
			pv.removeAllElements();
			pv.addElement( StackMachineInstruction.make( "halt", 
								operands));
			return;
		}
		pv.addElement(instr);
		s = op;
	}
	try{	//ora leggo l'argomento dello statement datablock
		s = t.nextToken();
		i = Integer.parseInt(s);
	}catch( RuntimeException e ){
		System.out.println( "ESMC lexical analysis error: " +
			" error in the datablock statement argument ");
		DS.setBase( lines );
		return;
	}
	/* DS base-addr = CS.size = lines */
	DS.setBase( lines );
	/* DS length : è contenuta in i   */
	dv.init(i);
	System.out.println( "lines= " + lines + " =? CS.size= " +pv.size());
	System.out.println( "datablock N= " +i +" =? DS.size= " +dv.size());
  }//caricaCSDS

}//StackMachine


/********/


class Register{
  protected int contents;
  public Register()		{ contents=0; }
  public Register(int c)	{ contents=c; }
  public void reset()		{ contents=0; }
  public int  contents()	{ return contents;}
  public void setContents(int val){ contents=val; }
  public String toString()	{ return Integer.toString( contents() );}
}//Register

/********/

class ProgramCounter extends Register{
  // un ProgramCounter è un Reg. dotato di circuito di autoincremento
  public void inc() { contents++; }
}//ProgramCounter

/********/

class FlagRegister extends Register{
  //
  //		[..............NZ]
  //
  // Z: flag di zero     (=true se risult. Alt è 0)
  // N. flag di negativo (=true se risult. Alu è neg)

  protected static final byte Z_mask = 1;
  protected static final byte N_mask = 2;

  public FlagRegister(){ super();}
  public FlagRegister(boolean newZ, boolean newN){
	super( (newN ? 2:0) + (newZ ? 1:0) );
  }
  public void reset(){ super.reset();}
  public boolean Z(){ return (contents() & Z_mask)==Z_mask ? true:false ;}
  public boolean N(){ return (contents() & N_mask)==N_mask ? true:false ;}
  public void setZ( Register TR ){
	setContents( (TR.contents()==0) ? 
		(contents()|Z_mask) : (contents() & ~Z_mask) );
  }
  public void setN( Register TR ){
	setContents( (TR.contents()< 0) ? 
		(contents()|N_mask) : (contents() & ~N_mask) );
  }
  public String toString(){return "000000"+ Integer.toBinaryString(contents());}
}//FlagRegister

/********/

class InstructionRegister{
  private Instruction I;
  // L'IR contiene una copia dell'istruzione letta all'ind. CS:PC.
  public InstructionRegister( Instruction inst ){ I = inst.copia();}
  public InstructionRegister( CodeSegment CS, ProgramCounter PC ){
	try{
		I =  CS.read(PC).copia();
	}
	catch( InvalidCodeReadAddressException icrae ){
		//è impossibile che sia vuoto (male che vada c'è HALT)
	}
  }
  public void setContents( Instruction newIref ){ I = newIref.copia();}
  public Instruction contents(){ return I; }
  public void reset( CodeSegment CS, ProgramCounter PC ){
	try{
		setContents( CS.read(PC) );
	}
	catch( InvalidCodeReadAddressException icrae ){
		//è impossibile che sia vuoto (male che vada c'è HALT)
	}
  }
  public String toString(){ return I.toString(); }
}//InstructionRegister

/********/

class Alu{
  // definitions of operation codes
  public static final byte add = 0;
  public static final byte sub = 1;
  public static final byte mul = 2;
  public static final byte div = 3;
  public static final byte and = 4;
  public static final byte or  = 5;
  public static final byte impl= 6;

  public Alu(){}
  public void reset(){}
  public void enable( byte opcode, Register TR1, Register TR2, Register TR3){
	switch( opcode ){
	  case add:	TR3.setContents( TR1.contents() + TR2.contents() );
			break;
	  case sub:	TR3.setContents( TR1.contents() - TR2.contents() );
			break;
	  case mul:	TR3.setContents( TR1.contents() * TR2.contents() );
			break;
	  case div:	TR3.setContents( TR1.contents() / TR2.contents() );
			break;
	  case and:	TR3.setContents( (
				   ( (TR1.contents()==0) ? false:true ) &&
				   ( (TR2.contents()==0) ? false:true )
					 ) ? 1:0 );
			break;
	  case or:	TR3.setContents( (
				   ( (TR1.contents()==0) ? false:true ) ||
				   ( (TR2.contents()==0) ? false:true )
					 ) ? 1:0 );
			break;
	  case impl:	TR3.setContents( elab_impl( TR1.contents(), 
						    TR2.contents() ) );
			break;
	  default: System.out.println("Invalid Alu opcode");
	}
  }

  private int elab_impl( int a, int b ){
	/* A => B  ==  ( !A || B ) */
	boolean A = ( (a==0) ? false:true );
	boolean B = ( (b==0) ? false:true );
	return ( (( !A || B) ? 1:0) );
  }

  public String toString(){ return "ready";}
}//Alu

/*********/

class IntStack extends Stack{
  public void push(Register R){ super.push(new Integer(R.contents()) );}
  public void pop(Register R) throws EmptyMachineStackException{
	try{
		Integer x= (Integer) super.pop();
		R.setContents(x.intValue());
	}
	catch( EmptyStackException ese ){
		throw new EmptyMachineStackException();
	}
  }

  public void reset(){
	while (!this.empty()){ Object x=this.pop(); }
  }

  public String stringView(){
	if (empty()) return toString();
	else return "element on top : " + peek().toString() + 
	 "  Complete vector: " + toString();
  }

  public String stringViewV(){
	if (empty()) return toString();
	else return "element on top : " + peek().toString() + 
	 "\n  Complete vector: " + toString();
  }
	
}//IntStack

