/*
Klasse:   MParse <BR>
Funktion: Parst einen String, der einen mathematischen Ausdruck in Infix-
          Notation enthält, und erstellt daraus eine Baumstruktur von
          Klassen, die den Operatoren und Operanden entsprechen. <BR>
Sprache:  JAVA <BR>
Autor:    Martin Klossek, Martin Meedt und Fabian Wleklinski <BR>*/
 
/**
Der mathematische Ausdruck, der geparst werden soll, muß der folgenden
Grammatik entsprechen: <BR>
<BR>
Start::=Ausdruck <BR>
Ausdruck::=Term {(Plus | Minus) Term}<BR>
Term::=Faktor {(Mal | Durch) Faktor}<BR>
Faktor::=Variable | NumKonstante | KlammerAuf Ausdruck KlammerZu | Funktion<BR>
Funktion::=Wort KlammerAuf Ausdruck KlammerZu
Variable::=Wort <BR>
NumKonstante::=[Minus] Ziffer {Ziffer} [Komma Ziffer {Ziffer}] <BR>
Ziffer::='0'|'1'|'2'|...|'9'<BR>
Buchstabe::='a'|'b'|...|'z'|'A'|'B'|...|'Z' <BR>
Wort::=Buchstabe {Buchstabe} <BR>
Komma::=',' <BR>
KlammerAuf::='(' <BR>
KlammerZu::=')' <BR>
Plus::='+' <BR>
Minus::='-' <BR>
Durch::='/' <BR>
Mal::='*' <BR>
*/
public class MParse {
  /** Erzeugt eine Instanz. Keine Parameter. */
  public MParse() {
    // instanzieren
    String src = new String();
  }

  /** Wertet den Ausdruck aus, und ersetzt dabei alle Vorkommen der Variable "X" durch den in Parameter "X"
  übergebenen Wert. Enthält der Ausdruck außer "X" auch andere Variablen, wird eine Exception geworfen. Enthält
  der Ausdruck undefinierte Funktionen, wird eine Exception geworfen. Der Wert der Auswertung wird zurückgegeben.
  @param X Der Wert für die Variable "X", an dem der Ausdruck ausgewertet werden soll.
  */
  public double evaluate( double x ) {
    return result.evaluate( x );
  }

  // Hält die Anzahl der Elemente, die in der Liste abgelegt wurden
  int nElements = 0;

  // Ist solange TRUE, wie der Ausdruck noch vereinfacht werden kann
  boolean geaendert = true;

  // Wir benötigen eine Liste, in der wir die erzeugten Klassen einfügen. Eigentlich
  // müsste die Liste dynamisch wachsen, aber ich begnüge mich mal mit einer
  // vernünftigen Länge von 2048 Einträgen
  Symbol[] tmpList;

  /** Bearbeitet den übergebenen String und versucht, den mathematischen Ausdruck durch
  eine interne Struktur zu repräsentieren. Werden eine ungültige Grammatik, oder nicht
  bekannte Zeichen verwendet, wird eine Exception geworfen. */
  public void parse( String src ) {
    this.src = src;

    /* Schritt 1 (Erkennung von Terminalsymbolen):
    - Der zu bearbeitende String muß in Infix-Notation mit der oben angegebenen 
    Grammatik angegeben sein. Diese Funktion wird den String Stück für Stück, 
    von vorne nach hinten durcharbeiten, und für jedes erkannte Terminalsymbol
    (Ziffern, Buchstaben, Operator, etc) ein Objekt von der zugehörigen Klasse
    instanziieren, un in einer Liste ablegen. Wenn ein Element nicht erkannt 
    werden kann, wird ein Fehler gemeldet. 
    - Diese Funktion wird auf den gesamten String angewendet
    
    Schritt 2 (Zusammenfassen von Ziffern zu NumKonstanten und Faktoren):
    - In diesem Schritt werden alle Kombinationen von Ziffern und Komma gemäß der 
    Übergangsregel NumKonstante::=[Minus] Ziffer {Ziffer} [',' {Ziffer}]
    ausgewertet. Da NumKonstanten zwangsläufig in Faktoren übergehen, ist
    dieser Übersetzungsschritt integriert, und die NumKonstanten werden gleich
    als Faktoren gespeichert.
    - Diese Funktion wird auf den gesamten Strinmg angewendet

    Schritt 3 (Zusammenfassen von Buchstaben zu Wörtern)
    in diesem Schritt werden alle Koombinationen von Buchstaben zu Wörtern 
    zusammengefasst.
    - Diese Funktion wird auf den gesamten Strinmg angewendet
    
    Schritt 4 (Anwendung der Grammatik und Erzeugung eines Baums):
    - Dieser Vorgang bearbeitet die entstandene Struktur, und versucht, durch die
    oben angegebene Grammatik beschriebene Konstellationen zu finden, z.B.
    "Faktor * Faktor". Wird eine solche Konstellation gefunden, wird eine neue
    Klasse vom Typ des übergeordneten Elementes, in diesem Beispiel "Term" 
    instanziiert. Diese wird die alte Konstellation ersetzen.
    - Diese Funktion wird nur auf Teilstrings angewendet, und zwar immer nur auf
    den Inhalt einer "Klammer", wobei innere Klammern vor äußeren Klammern 
    abgearbeitet werden.
    */

    /* Schritt 1 (Erkennung von Terminalsymbolen): */

    // Hält das aktuelle Zeichen des Strings, das bearbeitet wird
    char curChar;

    // Hält das neu erzeugte Symbol
    Symbol s;

    // Hiermit indizieren wir in der Liste
    int listIndex = 0;

    // FALSE, wenn das aktuelle Zeichen unbekannt ist
    boolean erkannt;

    // Wir benötigen eine Liste, in der wir die erzeugten Klassen einfügen. Eigentlich
    // müsste die Liste dynamisch wachsen, aber ich begnüge mich mal mit einer
    // vernünftigen Länge von 2048 Einträgen
    tmpList = new Symbol[2048];

    // Alle Zeichen des String abarbeiten
    // ToDi: Die Schleife muß abgebrochen werden, wenn ein Fehler unterlaufen ist
    for (int i=0; i < src.length(); i++) {

      // Jetzt nach Terminalsymbolen suchen

      // das aktuelle Zeichen holen
      curChar = src.charAt( i );

      // davon ausgehen, daß das Zeichen unbekannt ist
      erkannt = false;

      // Eine Instanz für das neue Symbol erzeugen
      s = null;

      // Mit den erlaubten Terminalsymbolen vergleichen

      // Leerzeichen (lassen wir unter den Tisch fallen)
      if (curChar == ' ') { erkannt = true; };     

      // Buchstaben 
      if ( (((byte) curChar >= (byte) 'a') && ((byte) curChar <= (byte) 'z')) || 
           (((byte) curChar >= (byte) 'A') && ((byte) curChar <= (byte) 'Z')) ) {
        erkannt = true;
        s = new Buchstabe();
        ((Buchstabe) s).value = curChar;
      }

      // Ziffern
      if (((byte) curChar >= (byte) '0') && ((byte) curChar <= (byte) '9')) {
        erkannt = true;
        s = new Ziffer();
        ((Ziffer) s).value = (((byte) curChar) - (byte) '0');
      }

      // Operatoren
      if (curChar == '+') { erkannt = true; s = new Plus(); }
      if (curChar == '-') { erkannt = true; s = new Minus(); }
      if (curChar == '*') { erkannt = true; s = new Mal(); }
      if (curChar == '/') { erkannt = true; s = new Durch(); }
      // Klammern
      if (curChar == '(') { erkannt = true; s = new KlammerAuf(); }
      if (curChar == ')') { erkannt = true; s = new KlammerZu(); }
      // Komma
      if (curChar == ',') { erkannt = true; s = new Komma(); }

      // Wenn ein Symbol erzeugt wurde, das Symbol in der Liste speichern
      if (s != null) { 
        tmpList[ listIndex ] = s; 
        listIndex++; 
        nElements++;
      }

      // Wenn ein unbekanntes Zeichen gefunden wurde -> Fehler
      if (! erkannt) {
        // Fehler melden
        throw new MParseException( "Das folgende Zeichen wurde nicht erkannt: '" + curChar + "'. Bitte korrigieren Sie die Eingabe." );
      }

    } // Letztes Element der Liste erreicht


    // Schritt 2 (Zusammenfassen von Ziffern zu NumKonstanten):

    // Von dem ersten bis zu dem letzten Element durchlaufen
    for (int i=0; i<nElements; i++ ) {

      // Handelt es sich bei dem aktuellen Element um eine Ziffer?
      if (tmpList[ i ] instanceof Ziffer) {

        // Eine zweite Schleife durchlaufen, solange, bis keine Ziffer mehr gefunden wird
        int j=i;
        int d=1;
        double value = 0;
        boolean done = false;
        boolean komma = false;
        while (! done) {

          if (tmpList[ j ] instanceof Ziffer) {
            if (! komma) {
              value = value * 10 + ((Ziffer) tmpList[j]).value;
            } else {
              value = value + ((double) ((Ziffer) tmpList[j]).value) / d;
            }
          }

          if ((! (tmpList[ j ] instanceof Ziffer)) && (! (tmpList[ j ] instanceof Komma))) { done = true; }
          if (tmpList[ j ] instanceof Komma) { 
            if (! komma) { komma = true; } else { done = true; }
          }

          if (komma) { d = d * 10; }
          
          if (! done) { j++; }

          if (j == nElements) { done = true; }
        }

        // Index "i" ("j-1") zeigt jetzt auf die erste (letzte) Ziffer

        // Einen Faktor erzeugen
        Faktor faktor = new Faktor();
        // Eine NumKonstante erzeugen
        NumKonstante numKonstante = new NumKonstante();
        // Den Wert der NumKonstanten setzen
        numKonstante.value = value;
        // Die NumKonstante an den Faktor dranhängen
        faktor.v = numKonstante;
        // Den Faktor in die Liste einfügen
        tmpList[i] = faktor;

        // Die nachfolgenden Elemente verschieben
        for (int k=i+1; k<nElements; k++) {
          tmpList[k] = tmpList [k+(j-i-1)];
        }
        nElements = nElements - (j-i-1);

        i = -1;

      } // Ende (es handelt es sich um eine Ziffer)
       
    } // Letztes Element der Kette bearbeitet

    // Schritt 3 (Zusammenfassen von Buchstaben zu Wörtern)

    // Jetzt alle Buchstaben zu Wörtern zusammenfassen
    // Von dem ersten bis zu dem letzten Element durchlaufen
    for (int i=0; i<nElements; i++ ) {

      String str = "";

      // Handelt es sich bei dem aktuellen Element um einen Buchstaben?
      if (tmpList[ i ] instanceof Buchstabe) {

        // Eine zweite Schleife durchlaufen, solange, bis kein Buchstabe mehr gefunden wird
        int j=i;
        boolean done = false;
        boolean komma = false;
        while (! done) {
          if (tmpList[ j ] instanceof Buchstabe) {
            str = str + ((Buchstabe) tmpList[ j ]).value + "";
          } else {
            done = true;
          }
          if (! done) { j++; }

          if (j == nElements) { done = true; }
        }
        // Index "i" ("j-1") zeigt jetzt auf den ersten (letzten) Buchstaben
        // Eine NumKonstante mit diesem Wert erzeugen
        tmpList[i] = new Wort();
        ((Wort) tmpList[i]).value = str;

        // Die anderen Elemente löschen
        for (int k=i+1; k<nElements; k++) {
          tmpList[k] = tmpList [k+(j-i-1)];
        }
        nElements = nElements - (j-i-1);
      }
    } // Letztes Element der liste bearbeitet

    // Schritt 4 (Anwendung der Grammatik und Erzeugung eines Baums):

    // Dieser Schritt beinhaltet eine Rekursion, deswegen ist er in einer seperaten Methode ausgelagert

    // doGrammatik erwartet den Index des ersten und des letzten Elementes, daß es bearbeiten soll
    doGrammatik( 0, nElements-1 );

    if (tmpList[ 0 ] instanceof Ausdruck) {
      result = (Ausdruck) tmpList[ 0 ];
    } else {
      result = null;
    }


  }


  /* wendet die Grammatik auf einen Teil der Liste an; der Index des ersten und des letzten zu
  bearbeitenden Elementes muß übergeben werden */
  private int doGrammatik( int start, int end ) {

    /* Bevor wir diesen teil irgendwie verändern können dürfen, müssen wir alle inneren
    Klammern bearbeiten. Deswegen testen wir, ob eine innere Klammer existiert. Dazu suchen
    wir nach einer schließenden Klammer, und sobald wir eine gefunden haben, suchen wir die
    vorhergehende öffnende Klammer. Die Indizes dieser beiden Elemente merken wir uns, und
    rufen die Methode rekursiv mit diesen beiden Indizes auf, um die innere Klammer aufzulösen. */

    // Alle Elemente in dem angegebenen Bereich anschauen


    int endMoved = 0;


  weiter_1:
    for (int endPos=start; endPos <= end; endPos++) {
      // Ist es eine schließende Klammer?
      if (tmpList[ endPos ] instanceof KlammerZu) {
        // ... ja -> in der Elementliste zurücklaufen und eine öffnende Klammer suchen
        for (int startPos=endPos; startPos >= start; startPos--) {
          // Ist es eine öffnende Klammer?
          if (tmpList[ startPos ] instanceof KlammerAuf) {
            // ... ja -> die Methode rekursiv aufrufen
            int tmp = doGrammatik( startPos+1, endPos-1 );
            end = end - tmp;
            endMoved = endMoved + tmp;
            // Es reicht ja, wenn wir EINE öffnende Klammer gefunden haben!
            endPos = start-1;
            break;
          } // öffnende Klammer gefunden
        } // öffnende Klammer suchen
      } // schließende Klammer gefunden
    } // schließende Klammer suchen

    /* Wir können an dieser Stelle davon ausgehen, daß in dem zu bearbeitenden Bereich keine
    Klammern mehr existieren. (Wenn wir das erste mal hier angelangt sind, befinden wir uns
    ja auch in einer der innersten Klammern) */

    /* Jetzt alle Wörter in dem zu bearbeitenden Bereich zuerst in Variablen, und dann in Faktoren
    umwandeln. Dieser Schritt ist legal, weil es sich bei den Wörtern nicht um Funktionen handeln
    kann, denn Funktionen beinhalten ja Klammerung, und dann hätten wir sie ja schon längst in einen Faktor
    umgewandelt. */

    for (int i=start; i <= end; i++) {
      if (tmpList[ i ] instanceof Wort) {
        Variable v = new Variable();
        v.value = ((Wort) tmpList[ i ]).value;
        Faktor f = new Faktor();
        f.v = v;
        tmpList[ i ] = f;
      }
    }

    /* Die folgenden Operationen werden die Länge der Struktur verändern, daher ist es wichtig, nach
    der Anwendung einer Operation die folgenden Elemente in der Liste nach vorne zu ziehen. Diese
    Variable hält die Anzahl der Elemente, die ersetzt werden: */
    int replaced;
    

    /* Jetzt alle Ketten der Form "Faktor" {('*'|'/') "Faktor" } durch "Term" ersetzen */

    // Das erste zu bearbeitende Zeichen sitzt direkt nach der Klammer
    int pos = start;
    // Alle Zeichen bis direkt vor der schließenden Klammer durchlaufen
    while (pos <= end) {
      // Hält die Anzahl der Elemente, die gelöscht werden müssen
      replaced = 0;

      // Eine Produktkette beginnt mit [Faktor] 
      if (tmpList[pos] instanceof Faktor) {

        // Wir haben eine Produktkette gefunden -> einen Term erzeugen
        Term term = new Term();
        // Für den ersten Faktor in dieser Kette ein Symbol erzeugen
        TermItem termItem = new TermItem();
        termItem.item = tmpList[pos]; 
        replaced++;
        term.items.add( termItem );

        // Jetzt eine Schleife starten, die so lange läuft, bis alle Faktoren abgearbeitet sind
        int pos2 = pos+1;
        // Alle Zeichen bis direkt vor der schließenden Klammer durchlaufen
      Schleife_1:
        while (pos2 <= end) {
          // Ausschließen, daß wir über das Ende der Klammer hinweg operieren
          if (pos2+1 <= end) {
            // Zur Fortsetzung der Produktkette akzeptiern wir nur ('*'|'/') [Faktor]
            if (((tmpList[pos2] instanceof Mal) || (tmpList[pos2] instanceof Durch)) && (tmpList[pos2+1] instanceof Faktor)) {

              // Für den neuen Faktor ein Symbol erzeugen
              termItem = new TermItem();
              termItem.item = tmpList[pos2+1];
              term.items.add( termItem );

              // Im Falle einer Division ...
              if (tmpList[pos2] instanceof Durch) {
                termItem.isNeg = true;
              }

              replaced = replaced+2;
            } else {
              break Schleife_1;
            }
          }
          pos2=pos2+2;
        }

        // Das Element in die Liste einsetzen
        tmpList[ pos ] = term;


        // In "replaced" steht jetzt die Anzahl der Elemente, aus denen der Term besteht. Diese Elemente sind
        // überflüssig, weil ja ein Term eingefügt wurde, der sie ersetzt. "replaced-1" ist daher die Anzahl
        // der Elemente, um die die nachfolgenden Elemente verschoben werden müssen.
        if (replaced > 1) {
          for (pos2=pos+1; pos2+(replaced-1) < nElements; pos2++) {
            tmpList[ pos2 ] = tmpList[ pos2 + (replaced-1) ];
          }
        }
        end = end - (replaced-1);
        nElements = nElements - (replaced - 1);
        pos = start-1;
        endMoved = endMoved + (replaced - 1);
      }


      pos++;
    }

    // Jetzt Termketten durch Ausdrücke ersetzen. 
    // Ausdruck ::= Term {('+'|'-') Term }

    // Das erste zu bearbeitende Zeichen sitzt direkt nach der Klammer
    pos = start;
    // Alle Zeichen bis direkt vor der schließenden Klammer durchlaufen
    while (pos <= end) {

      // Hält die Anzahl der Elemente, die gelöscht werden müssen
      replaced = 0;

      // Eine Kette beginnt mit [Term] || [Plus][Term] || [Minus][Term]
      if ( (tmpList[pos] instanceof Term) || ((tmpList[pos] instanceof Plus) && (tmpList[pos+1] instanceof Term)) || ((tmpList[pos] instanceof Minus) && (tmpList[pos+1] instanceof Term)) ) {

        boolean minus = (tmpList[pos] instanceof Minus);
        boolean hatVorzeichen = ! (tmpList[pos] instanceof Term);

        // Wir haben eine Termkette gefunden -> einen Ausdruck erzeugen
        Ausdruck ausdruck = new Ausdruck();
        
        // Für den ersten Summanden in dieser Kette ein Symbol erzeugen
        Term term;
        if (hatVorzeichen) {
          term = (Term) tmpList[ pos+1 ];
          if (minus) { term.isNeg = ! term.isNeg; }
          replaced++;
        } else {
          term = (Term) tmpList[ pos ];
        }
        ausdruck.items.add( term );
        replaced++;

        // Jetzt eine Schleife starten, die so lange läuft, bis alle Terme abgearbeitet sind
        int pos2 = pos+1;
        if (hatVorzeichen) { pos2++; }
        // Alle Zeichen bis direkt vor der schließenden Klammer durchlaufen
      Schleife_2:
        while (pos2 <= end) {
          // Ausschließen, daß wir über das Ende der Klammer hinweg operieren
          if (pos2+1 <= end) {
            // Zur Fortsetzung der Termkette akzeptiern wir nur ('+'|'-') [Term]
            if (((tmpList[pos2] instanceof Plus) || (tmpList[pos2] instanceof Minus)) && (tmpList[pos2+1] instanceof Term)) {

              // Für den neuen Ausdruck ein Symbol erzeugen
              term = (Term) tmpList[pos2+1];
              if (tmpList[pos2] instanceof Minus) { term.isNeg = ! term.isNeg; }
              ausdruck.items.add( term );

              replaced++;
              replaced++;
            } else {
              break Schleife_2;
            }
          }
          pos2=pos2+2;
        }

        // Das Element in die Liste einsetzen
        tmpList[ pos ] = ausdruck;

        // In "replaced" steht jetzt die Anzahl der Elemente, aus denen der Term besteht. Diese Elemente sind
        // überflüssig, weil ja ein Term eingefügt wurde, der sie ersetzt. "replaced-1" ist daher die Anzahl
        // der Elemente, um die die nachfolgenden Elemente verschoben werden müssen.
        if (replaced > 1) {
          for (pos2=pos+1; pos2+(replaced-1) < nElements; pos2++) {
            tmpList[ pos2 ] = tmpList[ pos2 + (replaced-1) ];
          }
        }
        end = end - (replaced-1);
        nElements = nElements - (replaced - 1);
        pos = start-1;
        endMoved = endMoved + (replaced - 1);
      }


    pos++;
    }

    /* An dieser Stelle sollte der Inhalt der bearbeiteten Klammer zu "KlammerAuf" "Ausdruck" "KlammerZu" 
    bearbeitet sein. Ist dies nicht der Fall, war die Grammatik des Klammerinhaltes fehlerhaft, und ein
    Fehler muß gemeldet werden. */

    if (start > 0) {
      if ((!(tmpList[start-1] instanceof KlammerAuf)) || (!(tmpList[start] instanceof Ausdruck)) || (!(tmpList[start+1] instanceof KlammerZu))) {
        // Fehler melden
        throw new MParseException( "Der eingegebene Ausdruck verwendet eine ungültige Grammatik. Bitte korrigieren Sie die Eingabe." );
      }
    } else {
      if ((!(tmpList[start] instanceof Ausdruck)) || (nElements > 1)) {
        // Fehler melden
        throw new MParseException( "Der eingegebene Ausdruck verwendet eine ungültige Grammatik. Bitte korrigieren Sie die Eingabe." );
      }
    }

    // Wenn das Vereinfachungs-Ziel erreicht ist -> exit
    if (nElements == 1) {
      return endMoved;
    }

    /* Wenn wir hier angelangt sind, konnte die innere Klammer tatsächlich zu "KlammerAuf" "Ausdruck" "KlammerZu"
    aufgelöst werden. Aus dieser Konstruktion erzeugen wir jetzt einen Faktor (laut Grammatik). Falls vor der
    öffnenden Klammer ein Wort steht, so handelt es sich eindeutig um eine Funktion. In diesem Fall erzeugen wir
    zwar auch einen Faktor, aber einen, der die Funktion beinhaltet. */

    // Hält die Position in der Liste, an die der Faktor geschrieben werden muß
    int faktorPos = start;
    if (start > 0) { faktorPos--; }
    // Hält die Anzahl der Elemente, um die die nachfolgenden Elemente verschoben werden müssen
    int moveCount = 2;
 
    // "istFunktion" setzen, wenn es sich um eine Funktion handelt
    boolean istFunktion = false;
    if (start > 1) {
      if (tmpList[start-2] instanceof Wort) { 
        istFunktion = true; 
        faktorPos--; moveCount++; 
      }
    }

    // Einen Faktor erzeugen
    Faktor faktor = new Faktor();

    // Wenn es sich um eine Funktion handelt -> Funktion erzeugen und dem Faktor eingliedern
    if (istFunktion) {
      Funktion funktion = new Funktion();
      funktion.ausdruck = (Ausdruck) tmpList[start];
      funktion.name = ((Wort) tmpList[start-2]).value;
      faktor.v = funktion;
    } else {
      faktor.v = (Ausdruck) tmpList[start];
    }

    // Den Faktor in die Liste aufnehmen
    tmpList[faktorPos] = faktor;

    // Die nachfolgenden Elemente verschieben
    for (int k=faktorPos+1; k<nElements; k++) { 
      tmpList[k] = tmpList[k+moveCount]; 
    }
    nElements = nElements - moveCount;

    return moveCount + endMoved;
  }

  // Schreibt die Struktur der Liste auf die Standardausgabe, dient zu Debug-Zwecken
  private void zeigeListe( ) {
    System.out.println("");
    System.out.println("Grammatik zum momentanen Zeitpunkt der Auswertung:");
    System.out.println("--------------------------------------------------");
    System.out.println("");
    for (int x=0; x < nElements; x++) {
      if ( tmpList[ x ] != null) {
        System.out.println(tmpList[ x ].getClass().getName());
      } else {
        System.out.println("Null");
      }
    }
    System.out.println( );
  }

  /** Das oberste Element der Struktur, die den mathematischen Ausdruck beinhaltet. */
  public Symbol result;

  // Hält den zu parsenden String, wie mit 'setSrc' festgelegt
  private String src;
}

/* Die Klasse 'Symbol' ist die abstrakte Basisklasse für alle Terminalsymbole und für
alle nicht-Terminalsymbole, d.h. für alle Bestandteile des Termes. */
abstract class Symbol {
  // Gibt seinen Wert zurück. Falls unbekannte Funktionen enthalten sind, oder undefinierte
  // Variablen, so wird ein Fehler gemeldet.
  public double evaluate( double x ) {
    return 0;
  }

  /* Schreibt den Wert bzw. die Struktur auf die Standardausgabe. Der übergebene Wert
  'indent' bezeichnet den Einzug. */
  public void dump( int indent ) {
    // Den Einzug schreiben
    for (int i=1; i <= indent; i++) {
      System.out.print( " " );
    }
    // Den Wert schreiben
    System.out.print( this.getClass().getName() );
    // rekursiv für alle untergeordneten Elemente aufrufen

    // Die Zeile abschließen
    System.out.println();
  }
}

/* Jeder Bestandteil der Grammatik wird durch eine eigene Klasse symbolisiert.
Die Deklaration dieser Klassen folgt jetzt. */

class Ausdruck extends Symbol {
  // Wertet das Element (an der Stelle "X"==X) aus, und liefert den Wert zurück
  public double evaluate( double x ) {
    double result = 0;
    for (int termI=1; termI <= items.count; termI++) {
      result = result + ((Term) items.get(termI-1)).evaluate( x );
    }
    return result;
  }

  public Ausdruck() {
    items = new SimpleList();
  }
  SimpleList items;
}


class Komma extends Symbol {
}

class KlammerAuf extends Symbol {
}

class KlammerZu extends Symbol {
}

class Plus extends Symbol {
}

class Minus extends Symbol {
}

class Mal extends Symbol {
}

class Durch extends Symbol {
}

class TermItem {
  boolean isNeg;
  Symbol item;
}

class Term extends Symbol {
  public Term() {
    items = new SimpleList();
    isNeg = false;
  }
  boolean isNeg;
  // Wertet das Element (an der Stelle "X"==X) aus, und liefert den Wert zurück
  public double evaluate( double x ) {
    double result = 1;
    for (int faktorI=1; faktorI <= items.count; faktorI++) {
      TermItem termItem = (TermItem) items.get(faktorI-1);
      if (termItem.isNeg) {
        result = result / termItem.item.evaluate( x );
      } else {
        result = result * termItem.item.evaluate( x );
      }
    }
    if (isNeg) { result = -result; }
    return result;
  }
  SimpleList items;
}

class Faktor extends Symbol {
  Symbol v;
  // Wertet das Element (an der Stelle "X"==X) aus, und liefert den Wert zurück
  public double evaluate( double x ) {
    double result = v.evaluate( x );
    return result;
  }
}

class Buchstabe extends Symbol {
  // Definiert als: 'a'|'b'|...|'z'|'A'|'B'|...|'Z'
  // Hält den Wert
  public char value;
}

class Wort extends Symbol {
  public String value;
}

class Funktion extends Symbol {
  public String name;
  public Ausdruck ausdruck;

  public double evaluate( double x ) {
    // TRUE wenn die Funktion definiert ist
    boolean erkannt = false;

    // Hält den Rückgabewert
    double result = 0;

    if (name.equalsIgnoreCase("sin")) {
      erkannt = true;
      result = Math.sin( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("asin")) {
      erkannt = true;
      result = Math.asin( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("acos")) {
      erkannt = true;
      result = Math.acos( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("abs")) {
      erkannt = true;
      result = Math.abs( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("sig")) {
      erkannt = true;
      result = ausdruck.evaluate( x ) / Math.abs( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("atan")) {
      erkannt = true;
      result = Math.atan( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("cos")) {
      erkannt = true;
      result = Math.cos( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("tan")) {
      erkannt = true;
      result = Math.tan( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("round")) {
      erkannt = true;
      long res = Math.round( ausdruck.evaluate( x ) );
      result = (double) res;
    }
    if (name.equalsIgnoreCase("sqrt")) {
      erkannt = true;
      result = Math.sqrt( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("sqr")) {
      erkannt = true;
      result = ausdruck.evaluate( x ) * ausdruck.evaluate( x );
    }
    if (name.equalsIgnoreCase("exp")) {
      erkannt = true;
      result = Math.exp( ausdruck.evaluate( x ) );
    }
    if (name.equalsIgnoreCase("log")) {
      erkannt = true;
      result = Math.log( ausdruck.evaluate( x ) );
    }

    if (! erkannt) {
      // Fehler melden
      throw new MParseException( "Die Funktion '" + name + "()' ist nicht definiert. " );
    }

    return result;
  }
}

class Variable extends Symbol {
  public String value;

  public double evaluate( double x ) {
    // Hält den Rückgabewert
    double result = 0;
    boolean erkannt = false;

    if (value.equalsIgnoreCase("x")) {
      erkannt = true;
      result = x;
    };
    if (value.equalsIgnoreCase("pi")) {
      erkannt = true;
      result = Math.PI;
    };
    if (value.equalsIgnoreCase("e")) {
      erkannt = true;
      result = Math.exp(1);
    };
    if (value.equalsIgnoreCase("Random")) {
      erkannt = true;
      result = Math.random();
    };

    if (! erkannt) {
      // Fehler melden
      throw new MParseException( "Die Variable '" + value + "' ist nicht definiert. " );
    }
    return result;
  }
}

// Hält das Grammatik-Element 'NumKonstante'
class NumKonstante extends Symbol {
  // Definiert als: ['-']Ziffer{Ziffer}['.'{Ziffer}]

  // Hält den Wert
  public double value;
  // Wertet das Element (an der Stelle "X"==X) aus, und liefert den Wert zurück
  public double evaluate( double x ) {
    return value;
  }
}


// Hält das Grammatik-Element 'Fname'
class Fname extends Symbol {
  // Definiert als: 'sin'|'cos'|'tan'|'sqrt'|'exp'|'log'
  // Hält den Bezeichner der Funktion (z.B. "sin")
  public String value;
}

// Hält das Grammatik-Element 'Ziffer'
class Ziffer extends Symbol {
  // Hält den Wert der Ziffer
  public int value;
}

/* Enthält eine ganz einfache Liste */
class SimpleList {
  public SimpleList() {
    // Feste Größe, längere Terme / Ausdrücke kommen nicht vor
    items = new Object[ 64 ];
    count = 0;
  }

  public void add( Object item ) {
    items[ count++ ] = item;
  }

  public Object get( int index ) {
    return items[ index ];
  }

  public int count;

  Object[] items;
}

/* Enthält eine Exception, die ausgelöst wird, wenn beim Parsen oder beim Auswerten ein 
Fehler auftritt. Dies kann eine ungültige Grammatik, eine nicht definierte Variable, oder
eine nicht definierte Funktion sein. */
class MParseException extends RuntimeException {
  // Der Text, der den Fehler beschreibt 
  public String msg;
  // Erzeugt eine neue Instanz 
  public MParseException (String msg) {
    super();
    this.msg = msg;
  }
}