6.6 Einen Parser in Java implementieren

Den vollständigen Quelltext, den wir in der Vorlesung geschrieben haben, finden Sie in der Datei ArithmeticGrammar.java.

Ich möchte nun eine kontextfreie Grammatik für arithmetische Ausdrücke der Form ((31+402)*83) entwerfen. Der Einfachheit halber bestehe ich auf strenger Klammerung, so wäre (2*(1+2+3)) zum Beispiel nicht erlaubt. Unsere Grammatik soll allgemeine Dezimalzahlen darstellen können. Das Alphabet ist somit Σ={0,1,2,3,4,5,6,7,8,9,+,*,(,)}. Die Produktionsregeln sind:

(JustNumber)EN(Sum)E(E+E)(Product)E(E*E)(SingleDigit)ND(NumberDigit)NNDD0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Die Nichtterminale sind also E (Expression), N (Number) und D (Digit). Wir haben auch den einzelnen Produktionen Namen gegeben, bis auf die der Form Di. Was soll nun unser Parser tun? Er soll, gegeben ein Eingabewort wL, den Ableitungsbaum konstruieren, für ((31+402)*83) also

Wie wir diesen Baum in Java repräsentieren, darüber sprechen wir in einer Minute. Zuerst aber: wir wollen mit diesem Baum etwas Sinnvolles tun. Zum Beispiel auswerten, so dass am Ende eine Zahl rauskommt, im obigen Beispiel also (31+402)83=35939. Oder den Ausdruck umformen von Infix-Notation zu Präfixnotation, also (* (+ 31 402) 83). All dies wird sehr einfach sein, sobald wir den Ableitungsbaum als Datenstruktur vorliegen haben.

Eine Datenstruktur für Ableitungsbäume

Für meine Implementierung in Java erschaffe ich für jedes Nichtterminal X ein Interface und für jede Produktionsregel Xα eine Klasse, die das Interface X implementiert und α als Klassenvariable enthält.

  • interface Expression wird implementiert von
    • class Sum, die als Klassenvariable Exrepssion e1, e2 enthält,
    • class Product, die als Klassenvariable Exrepssion e1, e2 enthält,
    • class JustNumber, die als Klassenvariable nur eine Number number enthält;
  • interface Number wird implementiert von
    • class MultiDigitNumber, die als Klassenvariable eine Number und eine Digit erhält und
    • class SingleDigitNumber, die als Klassenvariable ein Digit enthält;
  • interface Digit wird implementiert von class DigitOne, class DigitTwo, class DigitThree, class DigitFour, class DigitFive, class DigitSix, class DigitSeven, class DigitEight und class DigitNine.
In unserem Anwendungsfall hat jedes Interface eine Methode public int toInt(). Interface Expression hat zusätzlich noch die Methode String toPrefixNotation(). Ich schreibe auch ein Über-Interface ParseObject, das alle Interfaces zusammenfasst. Um uns das Debugging zu erleichtern, überschreibe ich in jeder Klasse die Methode public String toString().