import mathe.FormattedOut; // aus meinem mathe-package
/**
 * Realisiert eine Registermaschine, wie sie in der Vorlesung
 * AlgoDat beschrieben wurde.<br>
 *
 * Keine &ouml;ffentlichen Felder mehr, daf&uuml;r auch weniger
 * Sicherheits&uuml;berpr&uuml;fungen w&auml;hrend des Programmlaufs.
 *
 * @author Christian Semrau, 17.12.1999
 * <a href="mailto:Christian.Semrau@Student.uni-magdeburg.de">
 * Christian.Semrau@Student.uni-magdeburg.de</a>
 */
class ChS_Registermaschine2 {

	// Alle Befehle als Strings
	private static final String[] kommandos = {
	"END",   "GOTO",  "IFGOTO", "LOAD",
	"CLOAD", "STORE", "ADD",    "CADD",
	"SUB",   "CSUB",  "MULT",   "CMULT",
	"DIV",   "CDIV",  "NOP"}; // NOP = No Operation
	// man wird sich wohl denken koennen, dass IFGOTO
	// der bedingte Sprungbefehl ist
	// ach ja, noch was zu IFGOTO: diese Registermaschine kennt
	// keinen Befehl IF c0==10 GOTO 2
	// es gibt nur IF c0==0 GOTO 2  <->  IFGOTO 2

	// die Opcodes (Operator-Codes) der einzelnen Befehle,
	// dh jeder Befehl kriegt eine Nummer, die statt des Textes
	// gespeichert wird
	private static final int
	END    = 0,	GOTO   = 1,	IFGOTO = 2,	LOAD   = 3,
	CLOAD  = 4,	STORE  = 5,	ADD    = 6,	CADD   = 7,
	SUB    = 8,	CSUB   = 9,	MULT   =10,	CMULT  =11,
	DIV    =12,	CDIV   =13,	NOP    =14;
	
	// welche sind Registerbefehle (fuer Ueberpruefung des Programms)
	// (Ausnahme: IFGOTO gehoert nicht in diese Liste)
	private static final int[] REGBEF = {LOAD,STORE,ADD,SUB,MULT,DIV};
	
	// welche sind No-Operand-Befehle (haben keinen Operanden)
	private static final int[] NOOPBEF = {END,NOP};

	/* HIER beginnt die Beschreibung der Registermaschine: */

	/**
	 * die Opcodes, jeder Befehl wird durch eine Zahl dargestellt
	 */
	private int[] befehl; 
	/**
	 * die Operanden, dh die Zahlen hinter den Befehlen
	 */
	private int[] param; 
	/**
	 * der Befehlszeiger, die Werte reichen von 1(=Start) bis Programml&auml;nge
	 */
	private int b;
	/**
	 * die Speicherregister (0=Akku)
	 */
	private int[] c;
	/**
	 * die Anzahl der bereits ausgef&uuml;hrten Befehle
	 */
	private int t;


/**
 * L&auml;dt ein Programm und die Speicherbelegung.
 *
 * Dem Objekt wird das Programm als String-Feld &uuml;bergeben, zusammen
 * mit der initialen Speicherbelegung. Der Befehlsz&auml;hler zeigt
 * standardm&auml;&szlig;ig auf den ersten Befehl (Zeile 1).
 * Operanden und Registerinhalte sind nichtnegative ganze Zahlen.
 *
 * @param programm das Programm, wie unter parse(String[]) beschrieben
 * @param speicher die Speicherbelegung, wie unter setzeRegister(int[]) beschrieben
 *
 * @exception NumberFormatException siehe parse und setzeRegister
 * @exception IllegalArgumentException siehe parse und setzeRegister<br>
 * auch wenn ein illegaler Registerzugriff oder Sprung im Programm ist<br>
 * oder das Programm nicht mit <code>END</code> endet<br>
 *
 * <b>"Alles was schiefgehen kann, wird schiefgehen." Murphys Gesetz</b>
 *
 * @see #parse(java.lang.String[])
 * @see #setzeRegister(int[])
 */
public ChS_Registermaschine2(String[] programm, int[] speicher)
throws NumberFormatException, IllegalArgumentException {
	parse(programm);
	setzeRegister(speicher);
	check();
}

/**
 * True, wenn das Programm beendet ist (der aktuelle Befehl ist END), false, wenn nicht.
 * @return boolean
 */
public boolean beendet() {
  return (befehl[b-1]==END);
}

/**
 * Gibt die Text-Darstellung des Befehls in Zeile i zur&uuml;ck.
 *
 * @return String
 * @param i die Zeile des Befehls, der disassembliert werden soll
 * @exception IllegalArgumentException wenn i auf eine nicht existierende Zeile zeigt
 */
private String befehlToText(int i)
throws IllegalArgumentException {
  int nr;
  try{
	nr = befehl[i-1];
  }catch(ArrayIndexOutOfBoundsException e){
	throw new IllegalArgumentException(
	"Befehl "+(i)+" existiert nicht (1.."+befehl.length+").");
  }

  String s;
  s = kommandos[nr];
  s = FormattedOut.left(s,6); // nur Kosmetik, kann ggf. weggelassen werden
  for (int n=0; n<NOOPBEF.length; n++)
	if (NOOPBEF[n]==nr) return s;
  if (isRegBef(befehl[i-1]))
	return s+" ["+FormattedOut.right(param[i-1],2)+"]";
  return s+"  "+FormattedOut.right(param[i-1],2);
  // FormattedOut.right(param[i-1],2) kann ersetzt werden durch param[i-1]
}

/**
 * Pr&uuml;ft auf illegale Operanden im Programm
 * nach der Programm&uuml;bernahme und der Registerzuweisung.
 *
 * @exception IllegalArgumentException
 * wenn ein Befehl auf ein nicht existierendes Register zugreift<br>
 * oder ein Sprungbefehl eine nicht existierende Zeile enth&auml;lt<br>
 * oder das Programm nicht mit <code>END</code> endet
 */
private void check()
throws IllegalArgumentException {
  for (int i = 0; i<befehl.length; i++) {
	if (isRegBef(befehl[i])) {
	  if (param[i]>=c.length)
		throw new IllegalArgumentException(
		"Moeglicher Zugriff ausserhalb des reservierten Bereichs "+
		"(Zeile "+(i+1)+" auf Register "+param[i]+")");
	}else
	if (befehl[i]==GOTO||befehl[i]==IFGOTO){
	  if (param[i]<1||param[i]>befehl.length)
		throw new IllegalArgumentException(
		"Moeglicher Sprung in nicht existierende Zeile "+
		"(Zeile "+(i+1)+" nach Zeile "+param[i]+")");
	}
  }
  if (befehl[befehl.length-1]!=END)
	throw new IllegalArgumentException(
	"Die letzte Anweisung des Programms muss END sein.");
}

/**
 * True, wenn der Befehl mit dem Opcode <code>opc</code> ein Registerbefehl ist.
 * @return boolean
 * @param b der Opcode, der &uuml;berpr&uuml;ft werden soll
 */
private boolean isRegBef(int opc) {
  for (int n=0; n<REGBEF.length; n++)
	if (REGBEF[n]==opc) return true;
  return false;
}

/**
 * &Uuml;bersetzt das als String-Feld &uuml;bergebene Programm in die
 * interne Darstellung.<br>
 *
 * Setzt den Befehlsz&auml;hler <code>b</code> und die Anzahl der
 * ausgef&uuml;hrten Befehle <code>t</code> zur&uuml;ck.<br>
 *
 * Befehl und Operand (Zahl) m&uuml;ssen durch Leerzeichen getrennt sein.
 * <code>END</code> und <code>NOP</code> brauchen keine Operanden.
 * Gross-/Kleinschreibung ist egal.<br>
 * L&auml;&szlig;t die Register unver&auml;ndert.
 *
 * @param programm das Programm
 * @exception IllegalArgumentException
 * wenn ein Befehl nicht bekannt ist 
 * @exception NumberFormatException
 * wenn ein Operand keine nichtnegative ganze Zahl ist
 */
void parse(String[] programm)
throws IllegalArgumentException, NumberFormatException {
  if (programm.length==0)
	throw new IllegalArgumentException(
	"Wie soll ich ein leeres Programm ausfuehren !?");

  String bef; int op;
  befehl = new int[programm.length];
  param = new int[programm.length];

  for(int i=0; i<programm.length; i++) {
	int p = programm[i].indexOf(" ");
	if (p>=0) {
	  bef = programm[i].substring(0,p).toUpperCase();
	  // bef ist der Teil vor dem ersten Leerzeichen
	  try {
		String ops = programm[i].substring(programm[i].lastIndexOf(" ")+1);
		// ops ist der Teil hinter dem letzten Leerzeichen
	    op = new Integer(ops).intValue();
	    // Umwandlung in eine Zahl
	  } catch(NumberFormatException e) {
	    throw new NumberFormatException(
		"Keine nichtnegative ganze Zahl: ("+(i+1)+") "+programm[i]);
	  }
	  if (op<0)
	    throw new NumberFormatException(
		"Keine nichtnegative ganze Zahl: ("+(i+1)+") "+programm[i]);
	} else {  // p<0, dh kein Leerzeichen in der Zeile
	  bef = programm[i].toUpperCase();
	  op = 0;
	}

	boolean known = false;
	for(int j=0; j<kommandos.length; j++) {
	  if (kommandos[j].equals(bef)) {
	    befehl[i] = j;  param[i] = op;
	    known = true;  break;
	  }
	}
	if (!known)
	  throw new IllegalArgumentException(
	  "Unbekannter Befehl: ("+(i+1)+") "+programm[i]);
  } // for(int i=0; i<programm.length; i++)

  b = 1; t = 0;
} // parse()

/**
 * F&uuml;hrt das Programm bis zum Ende aus, ohne die Konfigurationen auszugeben.
 * @see #step()
 */
public void run(){
  do {} while(step());
}

/**
 * Gibt die aktuelle Konfiguration auf die Konsole aus.
 */
public void schreibeKonfiguration(){
  System.out.print(FormattedOut.right(t,2)+": ("+FormattedOut.right(b,2)+"; ");
  for (int i=0; i<c.length; i++)
	System.out.print(FormattedOut.right(c[i],2)+", ");
  System.out.print("...) - ");
  System.out.print(befehlToText(b));
  if (isRegBef(befehl[b-1]))
	System.out.print(" (="+c[param[b-1]]+")");
  if (befehl[b-1]==IFGOTO)
	System.out.print( (c[0]==0) ? "  (jmp)" : "  (nojmp)");
  System.out.println();
}

/**
 * Gibt das komplette Programm (ohne den Registerinhalt) auf die Konsole aus.
 */
public void schreibeProgramm(){
  for (int i=1; i<=befehl.length; i++)
	System.out.println(FormattedOut.right(i,2)+" "+befehlToText(i));
}

/**
 * &Uuml;bernimmt die Speicherregister vom int-Feld.
 *
 * Setzt den Befehlsz&auml;hler zur&uuml;ck.
 * Reserviert genau so viele Register, wie Elemente in
 * <code>speicher</code> sind.
 * Mehr Register existieren dann nicht!
 *
 * @param speicher die Speicherregister (ohne Befehlsz&auml;hler)
 * @exception IllegalArgumentException
 * wenn speicher ein leeres Feld ist
 * @exception NumberFormatException
 * wenn ein Registerinhalt kleiner als 0 ist
 */
void setzeRegister(int[] speicher)
throws IllegalArgumentException, NumberFormatException {
  if (speicher.length==0)
	throw new IllegalArgumentException(
	"Es muss mindestens ein Register (den Akku) geben!");
  c = new int[speicher.length];
  for (int i=0; i<c.length; i++)
	if (speicher[i]<0)
	  throw new NumberFormatException(
	  "Register "+i+" kleiner als Null!");
	else
	  c[i]=speicher[i];
  b = 1;
}

/**
 * F&uuml;hrt den aktuellen Befehl aus und kehrt dann zur&uuml;ck.
 *
 * @return boolean - true = Programm l&auml;uft weiter, false = beendet
 */
public boolean step(){
  t++; // wieder ein Befehl mehr ausgefuehrt

  switch (befehl[b-1]) {
	case LOAD:  c[0] = c[param[b-1]]; break;
	case CLOAD: c[0] =   param[b-1];  break;
	case STORE: c[param[b-1]] = c[0]; break;
	case ADD:   c[0]+= c[param[b-1]]; break;
	case CADD:  c[0]+=   param[b-1];  break;
	case MULT:  c[0]*= c[param[b-1]]; break;
	case CMULT: c[0]*=   param[b-1];  break; 
	case DIV:   c[0]/= c[param[b-1]]; break;
	case CDIV:  c[0]/=   param[b-1];  break;
	case SUB:   c[0]-= c[param[b-1]]; if (c[0]<0) c[0]=0; break;
	case CSUB:  c[0]-=   param[b-1];  if (c[0]<0) c[0]=0; break;
	case NOP: break;
	case END: return false;

	case IFGOTO: if(c[0]!=0) break; // else fall-through
	case GOTO: b = param[b-1]; return true;
	//default: kann eigentlich nicht auftreten
  }
  b++;
  return true;
}
} // ChS_Registermaschine2